MokoOnyx v01.00.00 — initial release (successor to MokoCassiopeia)
Some checks failed
Standards Compliance / Secret Scanning (push) Successful in 3s
Standards Compliance / License Header Validation (push) Successful in 4s
Standards Compliance / Repository Structure Validation (push) Successful in 5s
Standards Compliance / Coding Standards Check (push) Failing after 3s
Standards Compliance / Version Consistency Check (push) Successful in 3s
Standards Compliance / Workflow Configuration Check (push) Failing after 2s
Standards Compliance / Documentation Quality Check (push) Successful in 3s
Standards Compliance / README Completeness Check (push) Successful in 3s
Standards Compliance / Git Repository Hygiene (push) Successful in 2s
Standards Compliance / Script Integrity Validation (push) Successful in 4s
Standards Compliance / Line Length Check (push) Failing after 4s
Standards Compliance / File Naming Standards (push) Successful in 2s
Standards Compliance / Insecure Code Pattern Detection (push) Successful in 3s
Standards Compliance / Code Complexity Analysis (push) Successful in 3s
Standards Compliance / Code Duplication Detection (push) Successful in 4s
Standards Compliance / Dead Code Detection (push) Successful in 3s
Standards Compliance / File Size Limits (push) Successful in 2s
CodeQL Security Scanning / Analyze (javascript) (push) Failing after 1m9s
Standards Compliance / Binary File Detection (push) Successful in 4s
CodeQL Security Scanning / Analyze (actions) (push) Failing after 1m11s
Standards Compliance / TODO/FIXME Tracking (push) Successful in 3s
Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 5s
Standards Compliance / Broken Link Detection (push) Successful in 5s
Standards Compliance / Unused Dependencies Check (push) Successful in 7s
Standards Compliance / API Documentation Coverage (push) Successful in 3s
Standards Compliance / Accessibility Check (push) Successful in 3s
Standards Compliance / Performance Metrics (push) Successful in 3s
Standards Compliance / Enterprise Readiness Check (push) Successful in 3s
Standards Compliance / Repository Health Check (push) Successful in 4s
Standards Compliance / Terraform Configuration Validation (push) Successful in 6s
CodeQL Security Scanning / Security Scan Summary (push) Successful in 1s
Standards Compliance / Compliance Summary (push) Successful in 1s
Repo Health / Access control (push) Successful in 1s
Auto-Update SHA Hash / Update SHA-256 Hash in updates.xml (release) Successful in 4s
Repo Health / Release configuration (push) Failing after 3s
Repo Health / Scripts governance (push) Successful in 3s
Repo Health / Repository health (push) Failing after 3s
Some checks failed
Standards Compliance / Secret Scanning (push) Successful in 3s
Standards Compliance / License Header Validation (push) Successful in 4s
Standards Compliance / Repository Structure Validation (push) Successful in 5s
Standards Compliance / Coding Standards Check (push) Failing after 3s
Standards Compliance / Version Consistency Check (push) Successful in 3s
Standards Compliance / Workflow Configuration Check (push) Failing after 2s
Standards Compliance / Documentation Quality Check (push) Successful in 3s
Standards Compliance / README Completeness Check (push) Successful in 3s
Standards Compliance / Git Repository Hygiene (push) Successful in 2s
Standards Compliance / Script Integrity Validation (push) Successful in 4s
Standards Compliance / Line Length Check (push) Failing after 4s
Standards Compliance / File Naming Standards (push) Successful in 2s
Standards Compliance / Insecure Code Pattern Detection (push) Successful in 3s
Standards Compliance / Code Complexity Analysis (push) Successful in 3s
Standards Compliance / Code Duplication Detection (push) Successful in 4s
Standards Compliance / Dead Code Detection (push) Successful in 3s
Standards Compliance / File Size Limits (push) Successful in 2s
CodeQL Security Scanning / Analyze (javascript) (push) Failing after 1m9s
Standards Compliance / Binary File Detection (push) Successful in 4s
CodeQL Security Scanning / Analyze (actions) (push) Failing after 1m11s
Standards Compliance / TODO/FIXME Tracking (push) Successful in 3s
Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 5s
Standards Compliance / Broken Link Detection (push) Successful in 5s
Standards Compliance / Unused Dependencies Check (push) Successful in 7s
Standards Compliance / API Documentation Coverage (push) Successful in 3s
Standards Compliance / Accessibility Check (push) Successful in 3s
Standards Compliance / Performance Metrics (push) Successful in 3s
Standards Compliance / Enterprise Readiness Check (push) Successful in 3s
Standards Compliance / Repository Health Check (push) Successful in 4s
Standards Compliance / Terraform Configuration Validation (push) Successful in 6s
CodeQL Security Scanning / Security Scan Summary (push) Successful in 1s
Standards Compliance / Compliance Summary (push) Successful in 1s
Repo Health / Access control (push) Successful in 1s
Auto-Update SHA Hash / Update SHA-256 Hash in updates.xml (release) Successful in 4s
Repo Health / Release configuration (push) Failing after 3s
Repo Health / Scripts governance (push) Successful in 3s
Repo Health / Repository health (push) Failing after 3s
All files renamed from mokocassiopeia to mokoonyx. Update server points to MokoOnyx repo. Bridge migration removed (clean standalone template). Version reset to 01.00.00. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
.editorconfig
Normal file
41
.editorconfig
Normal file
@@ -0,0 +1,41 @@
|
||||
# EditorConfig helps maintain consistent coding styles across different editors and IDEs
|
||||
# https://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
# Default settings — Tabs preferred, width = 2 spaces
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
|
||||
# PowerShell scripts — tabs, 2-space visual width
|
||||
[*.ps1]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
end_of_line = crlf
|
||||
|
||||
# Markdown files — keep trailing whitespace for line breaks
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# JSON / YAML files — tabs, 2-space visual width
|
||||
[*.{json,yml,yaml}]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
|
||||
# Makefiles — always tabs, default width
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
|
||||
# Windows batch scripts — keep CRLF endings
|
||||
[*.{bat,cmd}]
|
||||
end_of_line = crlf
|
||||
|
||||
# Shell scripts — ensure LF endings
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Force LF line endings for all text files
|
||||
* text=auto eol=lf
|
||||
|
||||
# Explicitly mark binary files
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.svg text eol=lf
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.zip binary
|
||||
*.gz binary
|
||||
*.tar binary
|
||||
1
.github/.mokostandards
vendored
Normal file
1
.github/.mokostandards
vendored
Normal file
@@ -0,0 +1 @@
|
||||
platform: waas-component
|
||||
304
.github/CLAUDE.md
vendored
Normal file
304
.github/CLAUDE.md
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: MokoStandards.Templates.GitHub
|
||||
INGROUP: MokoStandards.Templates
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
PATH: /templates/github/copilot-instructions.joomla.md.template
|
||||
VERSION: XX.YY.ZZ
|
||||
BRIEF: GitHub Copilot custom instructions template for Joomla/MokoWaaS governed repositories
|
||||
NOTE: Synced to .github/copilot-instructions.md in all Joomla/WaaS repos via bulk sync.
|
||||
Tokens replaced at sync time: MokoOnyx, https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx, {{EXTENSION_NAME}},
|
||||
{{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}}
|
||||
-->
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **🔧 AI Self-Update Required on First Use**
|
||||
>
|
||||
> This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards bulk-sync system
|
||||
> replaces them automatically at sync time; if you can still see them, sync has not yet run for
|
||||
> this repository — or this is a brand-new repo.
|
||||
>
|
||||
> **On your very first interaction in this repository, before answering any other question:**
|
||||
>
|
||||
> 1. Check whether any `{{TOKEN}}` placeholders remain in this file.
|
||||
> 2. If they do, locate the values as described in the table below and replace every occurrence
|
||||
> in **both** `.github/copilot-instructions.md` and `.github/CLAUDE.md`.
|
||||
> 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]`
|
||||
> through the closing `> ---` line) in both files.
|
||||
> 4. Tell the developer: *"I've self-updated the AI context files with this repository's
|
||||
> values — all set."*
|
||||
>
|
||||
> | Placeholder | Where to find the value |
|
||||
> |---|---|
|
||||
> | `MokoOnyx` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
|
||||
> | `https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx` | Full GitHub URL, e.g. `https://git.mokoconsulting.tech/MokoConsulting/<repo-name>` |
|
||||
> | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root |
|
||||
> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
|
||||
> | `{{EXTENSION_ELEMENT}}` | The `<element>` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
|
||||
>
|
||||
> ---
|
||||
|
||||
# MokoOnyx — GitHub Copilot Custom Instructions
|
||||
|
||||
## What This Repo Is
|
||||
|
||||
This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
|
||||
|
||||
Repository URL: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
Extension name: **{{EXTENSION_NAME}}**
|
||||
Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
|
||||
Platform: **Joomla 4.x / MokoWaaS**
|
||||
|
||||
---
|
||||
|
||||
## Primary Language
|
||||
|
||||
**PHP** (≥ 7.4) is the primary language for this Joomla extension. JavaScript may be used for frontend enhancements. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
|
||||
|
||||
---
|
||||
|
||||
## File Header — Always Required on New Files
|
||||
|
||||
Every new file needs a copyright header as its first content.
|
||||
|
||||
**PHP:**
|
||||
```php
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoOnyx.{{EXTENSION_TYPE}}
|
||||
* INGROUP: MokoOnyx
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
* PATH: /path/to/file.php
|
||||
* VERSION: XX.YY.ZZ
|
||||
* BRIEF: One-line description of purpose
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
```
|
||||
|
||||
**Markdown:**
|
||||
```markdown
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: MokoOnyx.Documentation
|
||||
INGROUP: MokoOnyx
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
PATH: /docs/file.md
|
||||
VERSION: XX.YY.ZZ
|
||||
BRIEF: One-line description
|
||||
-->
|
||||
```
|
||||
|
||||
**YAML / Shell / XML:** Use the appropriate comment syntax with the same fields. JSON files are exempt.
|
||||
|
||||
---
|
||||
|
||||
## Version Management
|
||||
|
||||
**`README.md` is the single source of truth for the repository version.**
|
||||
|
||||
- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`.
|
||||
- The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references.
|
||||
- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
|
||||
- Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only.
|
||||
|
||||
### Joomla Version Alignment
|
||||
|
||||
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
|
||||
|
||||
```xml
|
||||
<!-- In manifest.xml — must match README.md version -->
|
||||
<version>01.02.04</version>
|
||||
|
||||
<!-- In updates.xml — prepend a new <update> block for every release.
|
||||
Note: the backslash in version="4\.[0-9]+" is a literal backslash character
|
||||
in the XML attribute value. Joomla's update server treats the value as a
|
||||
regular expression, so \. matches a literal dot. -->
|
||||
<updates>
|
||||
<update>
|
||||
<name>{{EXTENSION_NAME}}</name>
|
||||
<version>01.02.04</version>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">
|
||||
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
|
||||
</downloadurl>
|
||||
</downloads>
|
||||
<targetplatform name="joomla" version="4\.[0-9]+" />
|
||||
</update>
|
||||
<!-- … older entries preserved below … -->
|
||||
</updates>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Joomla Extension Structure
|
||||
|
||||
```
|
||||
MokoOnyx/
|
||||
├── manifest.xml # Joomla installer manifest (root — required)
|
||||
├── updates.xml # Update server manifest (root — required, see below)
|
||||
├── site/ # Frontend (site) code
|
||||
│ ├── controller.php
|
||||
│ ├── controllers/
|
||||
│ ├── models/
|
||||
│ └── views/
|
||||
├── admin/ # Backend (admin) code
|
||||
│ ├── controller.php
|
||||
│ ├── controllers/
|
||||
│ ├── models/
|
||||
│ ├── views/
|
||||
│ └── sql/
|
||||
├── language/ # Language INI files
|
||||
├── media/ # CSS, JS, images (deployed to /media/{{EXTENSION_ELEMENT}}/)
|
||||
├── docs/ # Technical documentation
|
||||
├── tests/ # Test suite
|
||||
├── .github/
|
||||
│ ├── workflows/
|
||||
│ ├── copilot-instructions.md # This file
|
||||
│ └── CLAUDE.md
|
||||
├── README.md # Version source of truth
|
||||
├── CHANGELOG.md
|
||||
├── CONTRIBUTING.md
|
||||
├── LICENSE # GPL-3.0-or-later
|
||||
└── Makefile # Build automation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## updates.xml — Required in Repo Root
|
||||
|
||||
`updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
|
||||
|
||||
The `manifest.xml` must reference it via:
|
||||
```xml
|
||||
<updateservers>
|
||||
<server type="extension" priority="1" name="{{EXTENSION_NAME}}">
|
||||
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/main/updates.xml
|
||||
</server>
|
||||
</updateservers>
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
|
||||
- The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
|
||||
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
|
||||
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
|
||||
|
||||
---
|
||||
|
||||
## manifest.xml Rules
|
||||
|
||||
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
|
||||
- `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
|
||||
- Must include `<updateservers>` block pointing to this repo's `updates.xml`.
|
||||
- Must include `<files folder="site">` and `<administration>` sections.
|
||||
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
|
||||
|
||||
---
|
||||
|
||||
## GitHub Actions — Token Usage
|
||||
|
||||
Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
|
||||
|
||||
```yaml
|
||||
# ✅ Correct
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ❌ Wrong — never use these in workflows
|
||||
token: ${{ github.token }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MokoStandards Reference
|
||||
|
||||
This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). Authoritative policies:
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
|
||||
| [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
|
||||
| [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
|
||||
| [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
|
||||
| [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
|
||||
| [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
| Context | Convention | Example |
|
||||
|---------|-----------|---------|
|
||||
| PHP class | `PascalCase` | `MyController` |
|
||||
| PHP method / function | `camelCase` | `getItems()` |
|
||||
| PHP variable | `$snake_case` | `$item_id` |
|
||||
| PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
|
||||
| PHP class file | `PascalCase.php` | `ItemModel.php` |
|
||||
| YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
|
||||
| Markdown doc | `kebab-case.md` | `installation-guide.md` |
|
||||
|
||||
---
|
||||
|
||||
## Commit Messages
|
||||
|
||||
Format: `<type>(<scope>): <subject>` — imperative, lower-case subject, no trailing period.
|
||||
|
||||
Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build`
|
||||
|
||||
---
|
||||
|
||||
## Branch Naming
|
||||
|
||||
Format: `<prefix>/<MAJOR.MINOR.PATCH>[/description]`
|
||||
|
||||
Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/`
|
||||
|
||||
---
|
||||
|
||||
## Keeping Documentation Current
|
||||
|
||||
| Change type | Documentation to update |
|
||||
|-------------|------------------------|
|
||||
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
|
||||
| New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
|
||||
| New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
|
||||
| New or changed workflow | `docs/workflows/<workflow-name>.md` |
|
||||
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
|
||||
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
|
||||
|
||||
---
|
||||
|
||||
## Key Constraints
|
||||
|
||||
- Never commit directly to `main` — all changes go via PR, squash-merged
|
||||
- Never skip the FILE INFORMATION block on a new file
|
||||
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
|
||||
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
|
||||
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
|
||||
- Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
|
||||
55
.github/CODEOWNERS
vendored
Normal file
55
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# CODEOWNERS — require approval from jmiller-moko for protected paths
|
||||
# Synced from MokoStandards. Do not edit manually.
|
||||
#
|
||||
# Changes to these paths require review from the listed owners before merge.
|
||||
# Combined with branch protection (require PR reviews), this prevents
|
||||
# unauthorized modifications to workflows, configs, and governance files.
|
||||
|
||||
# ── Synced workflows (managed by MokoStandards — do not edit manually) ────
|
||||
/.github/workflows/deploy-dev.yml @jmiller-moko
|
||||
/.github/workflows/deploy-demo.yml @jmiller-moko
|
||||
/.github/workflows/deploy-manual.yml @jmiller-moko
|
||||
/.github/workflows/auto-release.yml @jmiller-moko
|
||||
/.github/workflows/auto-dev-issue.yml @jmiller-moko
|
||||
/.github/workflows/auto-assign.yml @jmiller-moko
|
||||
/.github/workflows/sync-version-on-merge.yml @jmiller-moko
|
||||
/.github/workflows/enterprise-firewall-setup.yml @jmiller-moko
|
||||
/.github/workflows/repository-cleanup.yml @jmiller-moko
|
||||
/.github/workflows/standards-compliance.yml @jmiller-moko
|
||||
/.github/workflows/codeql-analysis.yml @jmiller-moko
|
||||
/.github/workflows/repo_health.yml @jmiller-moko
|
||||
/.github/workflows/ci-joomla.yml @jmiller-moko
|
||||
/.github/workflows/update-server.yml @jmiller-moko
|
||||
/.github/workflows/deploy-manual.yml @jmiller-moko
|
||||
/.github/workflows/ci-dolibarr.yml @jmiller-moko
|
||||
/.github/workflows/publish-to-mokodolimods.yml @jmiller-moko
|
||||
/.github/workflows/changelog-validation.yml @jmiller-moko
|
||||
/.github/workflows/branch-freeze.yml @jmiller-moko
|
||||
# Custom workflows in .github/workflows/ not listed above are repo-owned.
|
||||
|
||||
# ── GitHub configuration ─────────────────────────────────────────────────
|
||||
/.github/ISSUE_TEMPLATE/ @jmiller-moko
|
||||
/.github/CODEOWNERS @jmiller-moko
|
||||
/.github/copilot.yml @jmiller-moko
|
||||
/.github/copilot-instructions.md @jmiller-moko
|
||||
/.github/CLAUDE.md @jmiller-moko
|
||||
/.github/.mokostandards @jmiller-moko
|
||||
|
||||
# ── Build and config files ───────────────────────────────────────────────
|
||||
/composer.json @jmiller-moko
|
||||
/phpstan.neon @jmiller-moko
|
||||
/Makefile @jmiller-moko
|
||||
/.ftpignore @jmiller-moko
|
||||
/.gitignore @jmiller-moko
|
||||
/.gitattributes @jmiller-moko
|
||||
/.editorconfig @jmiller-moko
|
||||
|
||||
# ── Governance documents ─────────────────────────────────────────────────
|
||||
/LICENSE @jmiller-moko
|
||||
/CONTRIBUTING.md @jmiller-moko
|
||||
/SECURITY.md @jmiller-moko
|
||||
/GOVERNANCE.md @jmiller-moko
|
||||
/CODE_OF_CONDUCT.md @jmiller-moko
|
||||
110
.github/ISSUE_TEMPLATE/adr.md
vendored
Normal file
110
.github/ISSUE_TEMPLATE/adr.md
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
name: Architecture Decision Record (ADR)
|
||||
about: Propose or document an architectural decision
|
||||
title: '[ADR] '
|
||||
labels: 'architecture, decision'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## ADR Number
|
||||
ADR-XXXX
|
||||
|
||||
## Status
|
||||
- [ ] Proposed
|
||||
- [ ] Accepted
|
||||
- [ ] Deprecated
|
||||
- [ ] Superseded by ADR-XXXX
|
||||
|
||||
## Context
|
||||
Describe the issue or problem that motivates this decision.
|
||||
|
||||
## Decision
|
||||
State the architecture decision and provide rationale.
|
||||
|
||||
## Consequences
|
||||
### Positive
|
||||
- List positive consequences
|
||||
|
||||
### Negative
|
||||
- List negative consequences or trade-offs
|
||||
|
||||
### Neutral
|
||||
- List neutral aspects
|
||||
|
||||
## Alternatives Considered
|
||||
### Alternative 1
|
||||
- Description
|
||||
- Pros
|
||||
- Cons
|
||||
- Why not chosen
|
||||
|
||||
### Alternative 2
|
||||
- Description
|
||||
- Pros
|
||||
- Cons
|
||||
- Why not chosen
|
||||
|
||||
## Implementation Plan
|
||||
1. Step 1
|
||||
2. Step 2
|
||||
3. Step 3
|
||||
|
||||
## Stakeholders
|
||||
- **Decision Makers**: @user1, @user2
|
||||
- **Consulted**: @user3, @user4
|
||||
- **Informed**: team-name
|
||||
|
||||
## Technical Details
|
||||
### Architecture Diagram
|
||||
```
|
||||
[Add diagram or link]
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
- Dependency 1
|
||||
- Dependency 2
|
||||
|
||||
### Impact Analysis
|
||||
- **Performance**: [Impact description]
|
||||
- **Security**: [Impact description]
|
||||
- **Scalability**: [Impact description]
|
||||
- **Maintainability**: [Impact description]
|
||||
|
||||
## Testing Strategy
|
||||
- [ ] Unit tests
|
||||
- [ ] Integration tests
|
||||
- [ ] Performance tests
|
||||
- [ ] Security tests
|
||||
|
||||
## Documentation
|
||||
- [ ] Architecture documentation updated
|
||||
- [ ] API documentation updated
|
||||
- [ ] Developer guide updated
|
||||
- [ ] Runbook created
|
||||
|
||||
## Migration Path
|
||||
Describe how to migrate from current state to new architecture.
|
||||
|
||||
## Rollback Plan
|
||||
Describe how to rollback if issues occur.
|
||||
|
||||
## Timeline
|
||||
- **Proposal Date**:
|
||||
- **Decision Date**:
|
||||
- **Implementation Start**:
|
||||
- **Expected Completion**:
|
||||
|
||||
## References
|
||||
- Related ADRs:
|
||||
- External resources:
|
||||
- RFCs:
|
||||
|
||||
## Review Checklist
|
||||
- [ ] Aligns with enterprise architecture principles
|
||||
- [ ] Security implications reviewed
|
||||
- [ ] Performance implications reviewed
|
||||
- [ ] Cost implications reviewed
|
||||
- [ ] Compliance requirements met
|
||||
- [ ] Team consensus achieved
|
||||
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug or issue with the project
|
||||
title: '[BUG] '
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Bug Description
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
|
||||
## Expected Behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
## Actual Behavior
|
||||
A clear and concise description of what actually happened.
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## Environment
|
||||
- **Project**: [e.g., MokoDoliTools, moko-cassiopeia]
|
||||
- **Version**: [e.g., 1.2.3]
|
||||
- **Platform**: [e.g., Dolibarr 18.0, Joomla 5.0]
|
||||
- **PHP Version**: [e.g., 8.1]
|
||||
- **Database**: [e.g., MySQL 8.0, PostgreSQL 14]
|
||||
- **Browser** (if applicable): [e.g., Chrome 120, Firefox 121]
|
||||
- **OS**: [e.g., Ubuntu 22.04, Windows 11]
|
||||
|
||||
## Additional Context
|
||||
Add any other context about the problem here.
|
||||
|
||||
## Possible Solution
|
||||
If you have suggestions on how to fix the issue, please describe them here.
|
||||
|
||||
## Checklist
|
||||
- [ ] I have searched for similar issues before creating this one
|
||||
- [ ] I have provided all the requested information
|
||||
- [ ] I have tested this on the latest stable version
|
||||
- [ ] I have checked the documentation and couldn't find a solution
|
||||
18
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
18
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 💼 Enterprise Support
|
||||
url: https://mokoconsulting.tech/enterprise
|
||||
about: Enterprise-level support and consultation services
|
||||
- name: 💬 Ask a Question
|
||||
url: https://mokoconsulting.tech/
|
||||
about: Get help or ask questions through our website
|
||||
- name: 📚 MokoStandards Documentation
|
||||
url: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
about: View our coding standards and best practices
|
||||
- name: 🔒 Report a Security Vulnerability
|
||||
url: https://git.mokoconsulting.tech/MokoConsulting/.github-private/security/advisories/new
|
||||
about: Report security vulnerabilities privately (for critical issues)
|
||||
- name: 💡 Community Discussions
|
||||
url: https://github.com/orgs/mokoconsulting-tech/discussions
|
||||
about: Join community discussions and Q&A
|
||||
52
.github/ISSUE_TEMPLATE/documentation.md
vendored
Normal file
52
.github/ISSUE_TEMPLATE/documentation.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: Documentation Issue
|
||||
about: Report an issue with documentation
|
||||
title: '[DOCS] '
|
||||
labels: 'documentation'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Documentation Issue
|
||||
|
||||
**Location**:
|
||||
<!-- Specify the file, page, or section with the issue -->
|
||||
|
||||
## Issue Type
|
||||
<!-- Mark the relevant option with an "x" -->
|
||||
- [ ] Typo or grammar error
|
||||
- [ ] Outdated information
|
||||
- [ ] Missing documentation
|
||||
- [ ] Unclear explanation
|
||||
- [ ] Broken links
|
||||
- [ ] Missing examples
|
||||
- [ ] Other (specify below)
|
||||
|
||||
## Description
|
||||
<!-- Clearly describe the documentation issue -->
|
||||
|
||||
## Current Content
|
||||
<!-- Quote or describe the current documentation (if applicable) -->
|
||||
```
|
||||
Current text here
|
||||
```
|
||||
|
||||
## Suggested Improvement
|
||||
<!-- Provide your suggestion for how to improve the documentation -->
|
||||
```
|
||||
Suggested text here
|
||||
```
|
||||
|
||||
## Additional Context
|
||||
<!-- Add any other context, screenshots, or references -->
|
||||
|
||||
## Standards Alignment
|
||||
- [ ] Follows MokoStandards documentation guidelines
|
||||
- [ ] Uses en_US/en_GB localization
|
||||
- [ ] Includes proper SPDX headers where applicable
|
||||
|
||||
## Checklist
|
||||
- [ ] I have searched for similar documentation issues
|
||||
- [ ] I have provided a clear description
|
||||
- [ ] I have suggested an improvement (if applicable)
|
||||
85
.github/ISSUE_TEMPLATE/enterprise_support.md
vendored
Normal file
85
.github/ISSUE_TEMPLATE/enterprise_support.md
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
name: Enterprise Support Request
|
||||
about: Request enterprise-level support or consultation
|
||||
title: '[ENTERPRISE] '
|
||||
labels: 'enterprise, support'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Support Request Type
|
||||
- [ ] Critical Production Issue
|
||||
- [ ] Performance Optimization
|
||||
- [ ] Security Audit
|
||||
- [ ] Architecture Review
|
||||
- [ ] Custom Development
|
||||
- [ ] Migration Support
|
||||
- [ ] Training & Onboarding
|
||||
- [ ] Other (please specify)
|
||||
|
||||
## Priority Level
|
||||
- [ ] P0 - Critical (Production Down)
|
||||
- [ ] P1 - High (Major Feature Broken)
|
||||
- [ ] P2 - Medium (Non-Critical Issue)
|
||||
- [ ] P3 - Low (Enhancement/Question)
|
||||
|
||||
## Organization Details
|
||||
- **Company Name**:
|
||||
- **Contact Person**:
|
||||
- **Email**:
|
||||
- **Phone** (for P0/P1 issues):
|
||||
- **Timezone**:
|
||||
|
||||
## Issue Description
|
||||
Provide a clear and detailed description of your request or issue.
|
||||
|
||||
## Business Impact
|
||||
Describe the impact on your business operations:
|
||||
- Number of users affected:
|
||||
- Revenue impact (if applicable):
|
||||
- Deadline/SLA requirements:
|
||||
|
||||
## Environment Details
|
||||
- **Deployment Type**: [On-Premise / Cloud / Hybrid]
|
||||
- **Platform**: [Joomla / Dolibarr / Custom]
|
||||
- **Version**:
|
||||
- **Infrastructure**: [AWS / Azure / GCP / Other]
|
||||
- **Scale**: [Users / Transactions / Data Volume]
|
||||
|
||||
## Current Configuration
|
||||
```yaml
|
||||
# Paste relevant configuration (sanitize sensitive data)
|
||||
```
|
||||
|
||||
## Logs and Diagnostics
|
||||
```
|
||||
# Paste relevant logs (sanitize sensitive data)
|
||||
```
|
||||
|
||||
## Attempted Solutions
|
||||
Describe any troubleshooting steps already taken.
|
||||
|
||||
## Expected Resolution
|
||||
Describe your expected outcome or resolution.
|
||||
|
||||
## Additional Resources
|
||||
- **Documentation Links**:
|
||||
- **Related Issues**:
|
||||
- **Screenshots/Videos**:
|
||||
|
||||
## Enterprise SLA
|
||||
- [ ] Standard Support (initial response within 1–3 weeks)
|
||||
- [ ] Premium Support (initial response within 5 business days)
|
||||
- [ ] Critical Support (initial response within 72 hours)
|
||||
- [ ] Custom SLA (specify):
|
||||
|
||||
## Compliance Requirements
|
||||
- [ ] GDPR
|
||||
- [ ] HIPAA
|
||||
- [ ] SOC 2
|
||||
- [ ] ISO 27001
|
||||
- [ ] Other (specify):
|
||||
|
||||
---
|
||||
**Note**: Enterprise support requests require an active support contract. If you don't have one, please contact us at enterprise@mokoconsulting.tech
|
||||
51
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
51
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature or enhancement
|
||||
title: '[FEATURE] '
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Feature Description
|
||||
A clear and concise description of the feature you'd like to see.
|
||||
|
||||
## Problem or Use Case
|
||||
Describe the problem this feature would solve or the use case it addresses.
|
||||
Ex. I'm always frustrated when [...]
|
||||
|
||||
## Proposed Solution
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
## Alternative Solutions
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
## Benefits
|
||||
Describe how this feature would benefit users:
|
||||
- Who would use this feature?
|
||||
- What problems does it solve?
|
||||
- What value does it add?
|
||||
|
||||
## Implementation Details (Optional)
|
||||
If you have ideas about how this could be implemented, share them here:
|
||||
- Technical approach
|
||||
- Files/components that might need changes
|
||||
- Any concerns or challenges you foresee
|
||||
|
||||
## Additional Context
|
||||
Add any other context, mockups, or screenshots about the feature request here.
|
||||
|
||||
## Relevant Standards
|
||||
Does this relate to any standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
||||
- [ ] Accessibility (WCAG 2.1 AA)
|
||||
- [ ] Localization (en_US/en_GB)
|
||||
- [ ] Security best practices
|
||||
- [ ] Code quality standards
|
||||
- [ ] Other: [specify]
|
||||
|
||||
## Checklist
|
||||
- [ ] I have searched for similar feature requests before creating this one
|
||||
- [ ] I have clearly described the use case and benefits
|
||||
- [ ] I have considered alternative solutions
|
||||
- [ ] This feature aligns with the project's goals and scope
|
||||
190
.github/ISSUE_TEMPLATE/firewall-request.md
vendored
Normal file
190
.github/ISSUE_TEMPLATE/firewall-request.md
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
---
|
||||
name: Firewall Request
|
||||
about: Request firewall rule changes or access to external resources
|
||||
title: '[FIREWALL] [Resource Name] - [Brief Description]'
|
||||
labels: ['firewall-request', 'infrastructure', 'security']
|
||||
assignees: []
|
||||
---
|
||||
|
||||
|
||||
## Firewall Request
|
||||
|
||||
### Request Type
|
||||
- [ ] Allow outbound access to external service/API
|
||||
- [ ] Allow inbound access from external source
|
||||
- [ ] Modify existing firewall rule
|
||||
- [ ] Remove/revoke firewall rule
|
||||
- [ ] Other (specify):
|
||||
|
||||
### Resource Information
|
||||
**Service/Domain Name**:
|
||||
**IP Address(es)**:
|
||||
**Port(s)**:
|
||||
**Protocol**:
|
||||
- [ ] HTTP (80)
|
||||
- [ ] HTTPS (443)
|
||||
- [ ] SSH (22)
|
||||
- [ ] FTP (21)
|
||||
- [ ] SFTP (22)
|
||||
- [ ] Custom (specify): _______________
|
||||
|
||||
### Requestor Information
|
||||
**Name**:
|
||||
**GitHub Username**: @
|
||||
**Email**: @mokoconsulting.tech
|
||||
**Team/Department**:
|
||||
**Manager**: @
|
||||
|
||||
### Business Justification
|
||||
**Why is this access needed?**
|
||||
|
||||
**Which project(s) require this access?**
|
||||
|
||||
**What functionality will break without this access?**
|
||||
|
||||
**Is there an alternative solution?**
|
||||
- [ ] Yes (explain):
|
||||
- [ ] No
|
||||
|
||||
### Security Considerations
|
||||
**Data Classification**:
|
||||
- [ ] Public
|
||||
- [ ] Internal
|
||||
- [ ] Confidential
|
||||
- [ ] Restricted
|
||||
|
||||
**Sensitive Data Transmission**:
|
||||
- [ ] No sensitive data will be transmitted
|
||||
- [ ] Sensitive data will be transmitted (encryption required)
|
||||
- [ ] Authentication credentials will be transmitted (secure storage required)
|
||||
|
||||
**Third-Party Service**:
|
||||
- [ ] This is a trusted/verified third-party service
|
||||
- [ ] This is a new/unverified service (security review required)
|
||||
|
||||
**Service Documentation**:
|
||||
(Provide link to service documentation or API specs)
|
||||
|
||||
### Access Scope
|
||||
**Affected Systems**:
|
||||
- [ ] Development environment only
|
||||
- [ ] Staging environment only
|
||||
- [ ] Production environment
|
||||
- [ ] All environments
|
||||
|
||||
**Access Duration**:
|
||||
- [ ] Permanent (ongoing business need)
|
||||
- [ ] Temporary (specify end date): _______________
|
||||
- [ ] Testing only (specify duration): _______________
|
||||
|
||||
### Technical Details
|
||||
**Source System(s)**:
|
||||
(Which internal systems need access?)
|
||||
|
||||
**Destination System(s)**:
|
||||
(Which external systems need to be accessed?)
|
||||
|
||||
**Expected Traffic Volume**:
|
||||
(e.g., requests per hour/day)
|
||||
|
||||
**Traffic Pattern**:
|
||||
- [ ] Continuous
|
||||
- [ ] Periodic (specify frequency): _______________
|
||||
- [ ] On-demand/manual
|
||||
- [ ] Scheduled (specify schedule): _______________
|
||||
|
||||
### Testing Requirements
|
||||
**Pre-Production Testing**:
|
||||
- [ ] Request includes dev/staging access for testing
|
||||
- [ ] Testing can be done with production access only
|
||||
- [ ] No testing required (modify existing rule)
|
||||
|
||||
**Testing Plan**:
|
||||
|
||||
**Rollback Plan**:
|
||||
(What happens if access needs to be revoked?)
|
||||
|
||||
### Compliance & Audit
|
||||
**Compliance Requirements**:
|
||||
- [ ] GDPR considerations
|
||||
- [ ] SOC 2 compliance required
|
||||
- [ ] PCI DSS considerations
|
||||
- [ ] Other regulatory requirements: _______________
|
||||
- [ ] No specific compliance requirements
|
||||
|
||||
**Audit/Logging Requirements**:
|
||||
- [ ] Standard logging sufficient
|
||||
- [ ] Enhanced logging/monitoring required
|
||||
- [ ] Real-time alerting required
|
||||
|
||||
### Urgency
|
||||
- [ ] Critical (production down, immediate access needed)
|
||||
- [ ] High (needed within 24 hours)
|
||||
- [ ] Normal (needed within 1 week)
|
||||
- [ ] Low priority (needed within 1 month)
|
||||
|
||||
**If critical/high urgency, explain why:**
|
||||
|
||||
### Approvals
|
||||
**Manager Approval**:
|
||||
- [ ] Manager has been notified and approves this request
|
||||
|
||||
**Security Team Review Required**:
|
||||
- [ ] Yes (new external service, sensitive data)
|
||||
- [ ] No (minor change, established service)
|
||||
|
||||
### Additional Information
|
||||
|
||||
**Related Documentation**:
|
||||
(Links to relevant docs, RFCs, tickets, etc.)
|
||||
|
||||
**Dependencies**:
|
||||
(Other systems or changes this depends on)
|
||||
|
||||
**Comments/Questions**:
|
||||
|
||||
---
|
||||
|
||||
## For Infrastructure/Security Team Use Only
|
||||
|
||||
**Do not edit below this line**
|
||||
|
||||
### Security Review
|
||||
- [ ] Security team review completed
|
||||
- [ ] Risk assessment: Low / Medium / High
|
||||
- [ ] Encryption required: Yes / No
|
||||
- [ ] VPN required: Yes / No
|
||||
- [ ] Additional security controls: _______________
|
||||
|
||||
**Reviewed By**: @_______________
|
||||
**Review Date**: _______________
|
||||
**Review Notes**:
|
||||
|
||||
### Implementation
|
||||
- [ ] Firewall rule created/modified
|
||||
- [ ] Rule tested in dev/staging
|
||||
- [ ] Rule deployed to production
|
||||
- [ ] Monitoring/alerting configured
|
||||
- [ ] Documentation updated
|
||||
|
||||
**Firewall Rule ID**: _______________
|
||||
**Implementation Date**: _______________
|
||||
**Implemented By**: @_______________
|
||||
|
||||
**Configuration Details**:
|
||||
```
|
||||
Source:
|
||||
Destination:
|
||||
Port/Protocol:
|
||||
Action: Allow/Deny
|
||||
```
|
||||
|
||||
### Verification
|
||||
- [ ] Requestor confirmed access working
|
||||
- [ ] Logs reviewed (no anomalies)
|
||||
- [ ] Security scan completed (if applicable)
|
||||
|
||||
**Verification Date**: _______________
|
||||
**Verified By**: @_______________
|
||||
|
||||
### Notes
|
||||
87
.github/ISSUE_TEMPLATE/joomla_issue.md
vendored
Normal file
87
.github/ISSUE_TEMPLATE/joomla_issue.md
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: Joomla Extension Issue
|
||||
about: Report an issue with a Joomla extension
|
||||
title: '[JOOMLA] '
|
||||
labels: 'joomla'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Issue Type
|
||||
- [ ] Component Issue
|
||||
- [ ] Module Issue
|
||||
- [ ] Plugin Issue
|
||||
- [ ] Template Issue
|
||||
|
||||
## Extension Details
|
||||
- **Extension Name**: [e.g., moko-cassiopeia]
|
||||
- **Extension Version**: [e.g., 1.2.3]
|
||||
- **Extension Type**: [Component / Module / Plugin / Template]
|
||||
|
||||
## Joomla Environment
|
||||
- **Joomla Version**: [e.g., 4.4.0, 5.0.0]
|
||||
- **PHP Version**: [e.g., 8.1.0]
|
||||
- **Database**: [MySQL / PostgreSQL / MariaDB]
|
||||
- **Database Version**: [e.g., 8.0]
|
||||
- **Server**: [Apache / Nginx / IIS]
|
||||
- **Hosting**: [Shared / VPS / Dedicated / Cloud]
|
||||
|
||||
## Issue Description
|
||||
Provide a clear and detailed description of the issue.
|
||||
|
||||
## Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Configure '...'
|
||||
4. See error
|
||||
|
||||
## Expected Behavior
|
||||
What you expected to happen.
|
||||
|
||||
## Actual Behavior
|
||||
What actually happened.
|
||||
|
||||
## Error Messages
|
||||
```
|
||||
# Paste any error messages from Joomla error logs
|
||||
# Location: administrator/logs/error.php
|
||||
```
|
||||
|
||||
## Browser Console Errors
|
||||
```javascript
|
||||
// Paste any JavaScript console errors (F12 in browser)
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
Add screenshots to help explain the issue.
|
||||
|
||||
## Configuration
|
||||
```ini
|
||||
# Paste extension configuration (sanitize sensitive data)
|
||||
```
|
||||
|
||||
## Installed Extensions
|
||||
List other installed extensions that might conflict:
|
||||
- Extension 1 (version)
|
||||
- Extension 2 (version)
|
||||
|
||||
## Template Overrides
|
||||
- [ ] Using template overrides
|
||||
- [ ] Custom CSS
|
||||
- [ ] Custom JavaScript
|
||||
|
||||
## Additional Context
|
||||
- **Multilingual Site**: [Yes / No]
|
||||
- **Cache Enabled**: [Yes / No]
|
||||
- **Debug Mode**: [Yes / No]
|
||||
- **SEF URLs**: [Yes / No]
|
||||
|
||||
## Checklist
|
||||
- [ ] I have cleared Joomla cache
|
||||
- [ ] I have disabled other extensions to test for conflicts
|
||||
- [ ] I have checked Joomla error logs
|
||||
- [ ] I have tested with a default Joomla template
|
||||
- [ ] I have checked browser console for JavaScript errors
|
||||
- [ ] I have searched for similar issues
|
||||
- [ ] I am using a supported Joomla version
|
||||
82
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
82
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about usage, features, or best practices
|
||||
title: '[QUESTION] '
|
||||
labels: ['question']
|
||||
assignees: []
|
||||
---
|
||||
|
||||
|
||||
## Question
|
||||
|
||||
**Your question:**
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
**What are you trying to accomplish?**
|
||||
|
||||
|
||||
**What have you already tried?**
|
||||
|
||||
|
||||
**Category**:
|
||||
- [ ] Script usage
|
||||
- [ ] Configuration
|
||||
- [ ] Workflow setup
|
||||
- [ ] Documentation interpretation
|
||||
- [ ] Best practices
|
||||
- [ ] Integration
|
||||
- [ ] Other: __________
|
||||
|
||||
## Environment (if relevant)
|
||||
|
||||
**Your setup**:
|
||||
- Operating System:
|
||||
- Version:
|
||||
|
||||
## What You've Researched
|
||||
|
||||
**Documentation reviewed**:
|
||||
- [ ] README.md
|
||||
- [ ] Project documentation
|
||||
- [ ] Other (specify): __________
|
||||
|
||||
**Similar issues/questions found**:
|
||||
- #
|
||||
- #
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
**What result are you hoping for?**
|
||||
|
||||
|
||||
## Code/Configuration Samples
|
||||
|
||||
**Relevant code or configuration** (if applicable):
|
||||
|
||||
```bash
|
||||
# Your code here
|
||||
```
|
||||
|
||||
## Additional Context
|
||||
|
||||
**Any other relevant information:**
|
||||
|
||||
|
||||
**Screenshots** (if helpful):
|
||||
|
||||
|
||||
## Urgency
|
||||
|
||||
- [ ] Urgent (blocking work)
|
||||
- [ ] Normal (can work on other things meanwhile)
|
||||
- [ ] Low priority (just curious)
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have searched existing issues and discussions
|
||||
- [ ] I have reviewed relevant documentation
|
||||
- [ ] I have provided sufficient context
|
||||
- [ ] I have included code/configuration samples if relevant
|
||||
- [ ] This is a genuine question (not a bug report or feature request)
|
||||
107
.github/ISSUE_TEMPLATE/request-license.md
vendored
Normal file
107
.github/ISSUE_TEMPLATE/request-license.md
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
name: License Request
|
||||
about: Request an organization license for Sublime Text
|
||||
title: '[LICENSE REQUEST] Sublime Text - [Your Name]'
|
||||
labels: ['license-request', 'admin']
|
||||
assignees: []
|
||||
---
|
||||
|
||||
|
||||
## License Request
|
||||
|
||||
### Tool Information
|
||||
**Tool Name**: Sublime Text
|
||||
|
||||
**License Type Requested**: Organization Pool
|
||||
|
||||
**Personal Purchase**:
|
||||
- [ ] I prefer to purchase my own license ($99 USD - recommended, immediate access)
|
||||
- [ ] I prefer an organization license (1-2 business days, organization use only)
|
||||
- [ ] I have already purchased my own license (registration only for support)
|
||||
|
||||
### Requestor Information
|
||||
**Name**:
|
||||
**GitHub Username**: @
|
||||
**Email**: @mokoconsulting.tech
|
||||
**Team/Department**:
|
||||
**Manager**: @
|
||||
|
||||
### Justification
|
||||
**Why do you need this license?**
|
||||
|
||||
**Primary use case**:
|
||||
- [ ] Remote development (SFTP to servers)
|
||||
- [ ] Local development
|
||||
- [ ] Code review
|
||||
- [ ] Documentation editing
|
||||
- [ ] Other (specify):
|
||||
|
||||
**Which projects/repositories will you work on?**
|
||||
|
||||
**Have you evaluated the free trial?**
|
||||
- [ ] Yes, I've used the trial and Sublime Text meets my needs
|
||||
- [ ] No, requesting license before trial
|
||||
|
||||
**Alternative tools considered**:
|
||||
- [ ] VS Code (free alternative)
|
||||
- [ ] Vim/Neovim (free, terminal-based)
|
||||
- [ ] Other: _______________
|
||||
|
||||
### Platform
|
||||
- [ ] Windows
|
||||
- [ ] macOS
|
||||
- [ ] Linux (distribution: ________)
|
||||
|
||||
### Urgency
|
||||
- [ ] Urgent (needed within 24 hours - please justify)
|
||||
- [ ] Normal (1-2 business days)
|
||||
- [ ] Low priority (when available)
|
||||
|
||||
**If urgent, please explain why:**
|
||||
|
||||
### SFTP Plugin
|
||||
**Note**: Sublime SFTP plugin ($16 USD) is a **separate personal purchase** and is NOT provided by the organization.
|
||||
|
||||
- [ ] I understand SFTP plugin requires separate personal purchase
|
||||
- [ ] I have already purchased SFTP plugin
|
||||
- [ ] I will purchase SFTP plugin if needed for my work
|
||||
- [ ] I don't need SFTP plugin (local development only)
|
||||
|
||||
### Acknowledgments
|
||||
- [ ] I have read the License Management Policy (/docs/github-private/LICENSE_MANAGEMENT.md)
|
||||
- [ ] I understand organization licenses are for work use only
|
||||
- [ ] I understand organization licenses must be returned upon leaving
|
||||
- [ ] I understand personal purchases ($99) are an alternative with lifetime access
|
||||
- [ ] I understand SFTP plugin ($16) requires separate personal purchase
|
||||
- [ ] I agree to the terms of use
|
||||
|
||||
### Additional Information
|
||||
|
||||
**Expected daily usage hours**: _____ hours/day
|
||||
|
||||
**Duration of need**:
|
||||
- [ ] Permanent (ongoing role)
|
||||
- [ ] Temporary project (_____ months)
|
||||
- [ ] Trial/Evaluation (_____ weeks)
|
||||
|
||||
**Comments/Questions**:
|
||||
|
||||
---
|
||||
|
||||
## For Admin Use Only
|
||||
|
||||
**Do not edit below this line**
|
||||
|
||||
- [ ] Manager approval received (@manager-username)
|
||||
- [ ] License available in pool (current: __/20)
|
||||
- [ ] License type confirmed (Organization / Personal registration)
|
||||
- [ ] License key sent via encrypted email
|
||||
- [ ] Activation confirmed by user
|
||||
- [ ] Added to license tracking sheet
|
||||
- [ ] User notified of SFTP plugin requirement
|
||||
|
||||
**License Key ID**: _____________
|
||||
**Date Issued**: _____________
|
||||
**Issued By**: @_____________
|
||||
|
||||
**Notes**:
|
||||
126
.github/ISSUE_TEMPLATE/rfc.md
vendored
Normal file
126
.github/ISSUE_TEMPLATE/rfc.md
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
name: Request for Comments (RFC)
|
||||
about: Propose a significant change for community discussion
|
||||
title: '[RFC] '
|
||||
labels: 'rfc, discussion'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## RFC Summary
|
||||
One-paragraph summary of the proposal.
|
||||
|
||||
## Motivation
|
||||
Why are we doing this? What use cases does it support? What is the expected outcome?
|
||||
|
||||
## Detailed Design
|
||||
### Overview
|
||||
Provide a detailed explanation of the proposed change.
|
||||
|
||||
### API Changes (if applicable)
|
||||
```php
|
||||
// Before
|
||||
function oldApi($param1) { }
|
||||
|
||||
// After
|
||||
function newApi($param1, $param2) { }
|
||||
```
|
||||
|
||||
### User Experience Changes
|
||||
Describe how users will interact with this change.
|
||||
|
||||
### Implementation Approach
|
||||
High-level implementation strategy.
|
||||
|
||||
## Drawbacks
|
||||
Why should we *not* do this?
|
||||
|
||||
## Alternatives
|
||||
What other designs have been considered? What is the impact of not doing this?
|
||||
|
||||
### Alternative 1
|
||||
- Description
|
||||
- Trade-offs
|
||||
|
||||
### Alternative 2
|
||||
- Description
|
||||
- Trade-offs
|
||||
|
||||
## Adoption Strategy
|
||||
How will existing users adopt this? Is this a breaking change?
|
||||
|
||||
### Migration Guide
|
||||
```bash
|
||||
# Steps to migrate
|
||||
```
|
||||
|
||||
### Deprecation Timeline
|
||||
- **Announcement**:
|
||||
- **Deprecation**:
|
||||
- **Removal**:
|
||||
|
||||
## Unresolved Questions
|
||||
- Question 1
|
||||
- Question 2
|
||||
|
||||
## Future Possibilities
|
||||
What future work does this enable?
|
||||
|
||||
## Impact Assessment
|
||||
### Performance
|
||||
Expected performance impact.
|
||||
|
||||
### Security
|
||||
Security considerations and implications.
|
||||
|
||||
### Compatibility
|
||||
- **Backward Compatible**: [Yes / No]
|
||||
- **Breaking Changes**: [List]
|
||||
|
||||
### Maintenance
|
||||
Long-term maintenance considerations.
|
||||
|
||||
## Community Input
|
||||
### Stakeholders
|
||||
- [ ] Core team
|
||||
- [ ] Module developers
|
||||
- [ ] End users
|
||||
- [ ] Enterprise customers
|
||||
|
||||
### Feedback Period
|
||||
**Duration**: [e.g., 2 weeks]
|
||||
**Deadline**: [date]
|
||||
|
||||
## Implementation Timeline
|
||||
### Phase 1: Design
|
||||
- [ ] RFC discussion
|
||||
- [ ] Design finalization
|
||||
- [ ] Approval
|
||||
|
||||
### Phase 2: Implementation
|
||||
- [ ] Core implementation
|
||||
- [ ] Tests
|
||||
- [ ] Documentation
|
||||
|
||||
### Phase 3: Release
|
||||
- [ ] Beta release
|
||||
- [ ] Feedback collection
|
||||
- [ ] Stable release
|
||||
|
||||
## Success Metrics
|
||||
How will we measure success?
|
||||
- Metric 1
|
||||
- Metric 2
|
||||
|
||||
## References
|
||||
- Related RFCs:
|
||||
- External documentation:
|
||||
- Prior art:
|
||||
|
||||
## Open Questions for Community
|
||||
1. Question 1?
|
||||
2. Question 2?
|
||||
|
||||
---
|
||||
**Note**: This RFC is open for community discussion. Please provide feedback in the comments below.
|
||||
51
.github/ISSUE_TEMPLATE/security.md
vendored
Normal file
51
.github/ISSUE_TEMPLATE/security.md
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: Security Vulnerability Report
|
||||
about: Report a security vulnerability (use only for non-critical issues)
|
||||
title: '[SECURITY] '
|
||||
labels: 'security'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## ⚠️ IMPORTANT: Private Disclosure Required
|
||||
|
||||
**For critical security vulnerabilities, DO NOT use this template.**
|
||||
Follow the process in [SECURITY.md](../SECURITY.md) for responsible disclosure.
|
||||
|
||||
Use this template only for:
|
||||
- Security improvements
|
||||
- Non-critical security suggestions
|
||||
- Security documentation updates
|
||||
|
||||
---
|
||||
|
||||
## Security Issue
|
||||
|
||||
**Severity**:
|
||||
<!-- Low, Medium, or informational only -->
|
||||
|
||||
## Description
|
||||
<!-- Describe the security concern or improvement suggestion -->
|
||||
|
||||
## Affected Components
|
||||
<!-- List the affected files, features, or components -->
|
||||
|
||||
## Suggested Mitigation
|
||||
<!-- Describe how this could be addressed -->
|
||||
|
||||
## Standards Reference
|
||||
Does this relate to security standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
||||
- [ ] SPDX license identifiers
|
||||
- [ ] Secret management
|
||||
- [ ] Dependency security
|
||||
- [ ] Access control
|
||||
- [ ] Other: [specify]
|
||||
|
||||
## Additional Context
|
||||
<!-- Add any other context about the security concern -->
|
||||
|
||||
## Checklist
|
||||
- [ ] This is NOT a critical vulnerability requiring private disclosure
|
||||
- [ ] I have reviewed the SECURITY.md policy
|
||||
- [ ] I have provided sufficient detail for evaluation
|
||||
304
.github/copilot-instructions.md
vendored
Normal file
304
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: MokoStandards.Templates.GitHub
|
||||
INGROUP: MokoStandards.Templates
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
PATH: /templates/github/copilot-instructions.joomla.md.template
|
||||
VERSION: XX.YY.ZZ
|
||||
BRIEF: GitHub Copilot custom instructions template for Joomla/MokoWaaS governed repositories
|
||||
NOTE: Synced to .github/copilot-instructions.md in all Joomla/WaaS repos via bulk sync.
|
||||
Tokens replaced at sync time: MokoOnyx, https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx, {{EXTENSION_NAME}},
|
||||
{{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}}
|
||||
-->
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **🔧 AI Self-Update Required on First Use**
|
||||
>
|
||||
> This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards bulk-sync system
|
||||
> replaces them automatically at sync time; if you can still see them, sync has not yet run for
|
||||
> this repository — or this is a brand-new repo.
|
||||
>
|
||||
> **On your very first interaction in this repository, before answering any other question:**
|
||||
>
|
||||
> 1. Check whether any `{{TOKEN}}` placeholders remain in this file.
|
||||
> 2. If they do, locate the values as described in the table below and replace every occurrence
|
||||
> in **both** `.github/copilot-instructions.md` and `.github/CLAUDE.md`.
|
||||
> 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]`
|
||||
> through the closing `> ---` line) in both files.
|
||||
> 4. Tell the developer: *"I've self-updated the AI context files with this repository's
|
||||
> values — all set."*
|
||||
>
|
||||
> | Placeholder | Where to find the value |
|
||||
> |---|---|
|
||||
> | `MokoOnyx` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
|
||||
> | `https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx` | Full GitHub URL, e.g. `https://git.mokoconsulting.tech/MokoConsulting/<repo-name>` |
|
||||
> | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root |
|
||||
> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
|
||||
> | `{{EXTENSION_ELEMENT}}` | The `<element>` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
|
||||
>
|
||||
> ---
|
||||
|
||||
# MokoOnyx — GitHub Copilot Custom Instructions
|
||||
|
||||
## What This Repo Is
|
||||
|
||||
This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
|
||||
|
||||
Repository URL: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
Extension name: **{{EXTENSION_NAME}}**
|
||||
Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
|
||||
Platform: **Joomla 4.x / MokoWaaS**
|
||||
|
||||
---
|
||||
|
||||
## Primary Language
|
||||
|
||||
**PHP** (≥ 7.4) is the primary language for this Joomla extension. JavaScript may be used for frontend enhancements. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
|
||||
|
||||
---
|
||||
|
||||
## File Header — Always Required on New Files
|
||||
|
||||
Every new file needs a copyright header as its first content.
|
||||
|
||||
**PHP:**
|
||||
```php
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoOnyx.{{EXTENSION_TYPE}}
|
||||
* INGROUP: MokoOnyx
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
* PATH: /path/to/file.php
|
||||
* VERSION: XX.YY.ZZ
|
||||
* BRIEF: One-line description of purpose
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
```
|
||||
|
||||
**Markdown:**
|
||||
```markdown
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: MokoOnyx.Documentation
|
||||
INGROUP: MokoOnyx
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
PATH: /docs/file.md
|
||||
VERSION: XX.YY.ZZ
|
||||
BRIEF: One-line description
|
||||
-->
|
||||
```
|
||||
|
||||
**YAML / Shell / XML:** Use the appropriate comment syntax with the same fields. JSON files are exempt.
|
||||
|
||||
---
|
||||
|
||||
## Version Management
|
||||
|
||||
**`README.md` is the single source of truth for the repository version.**
|
||||
|
||||
- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`.
|
||||
- The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references.
|
||||
- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
|
||||
- Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only.
|
||||
|
||||
### Joomla Version Alignment
|
||||
|
||||
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
|
||||
|
||||
```xml
|
||||
<!-- In manifest.xml — must match README.md version -->
|
||||
<version>01.02.04</version>
|
||||
|
||||
<!-- In updates.xml — prepend a new <update> block for every release.
|
||||
Note: the backslash in version="4\.[0-9]+" is a literal backslash character
|
||||
in the XML attribute value. Joomla's update server treats the value as a
|
||||
regular expression, so \. matches a literal dot. -->
|
||||
<updates>
|
||||
<update>
|
||||
<name>{{EXTENSION_NAME}}</name>
|
||||
<version>01.02.04</version>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">
|
||||
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
|
||||
</downloadurl>
|
||||
</downloads>
|
||||
<targetplatform name="joomla" version="4\.[0-9]+" />
|
||||
</update>
|
||||
<!-- … older entries preserved below … -->
|
||||
</updates>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Joomla Extension Structure
|
||||
|
||||
```
|
||||
MokoOnyx/
|
||||
├── manifest.xml # Joomla installer manifest (root — required)
|
||||
├── updates.xml # Update server manifest (root — required, see below)
|
||||
├── site/ # Frontend (site) code
|
||||
│ ├── controller.php
|
||||
│ ├── controllers/
|
||||
│ ├── models/
|
||||
│ └── views/
|
||||
├── admin/ # Backend (admin) code
|
||||
│ ├── controller.php
|
||||
│ ├── controllers/
|
||||
│ ├── models/
|
||||
│ ├── views/
|
||||
│ └── sql/
|
||||
├── language/ # Language INI files
|
||||
├── media/ # CSS, JS, images (deployed to /media/{{EXTENSION_ELEMENT}}/)
|
||||
├── docs/ # Technical documentation
|
||||
├── tests/ # Test suite
|
||||
├── .github/
|
||||
│ ├── workflows/
|
||||
│ ├── copilot-instructions.md # This file
|
||||
│ └── CLAUDE.md
|
||||
├── README.md # Version source of truth
|
||||
├── CHANGELOG.md
|
||||
├── CONTRIBUTING.md
|
||||
├── LICENSE # GPL-3.0-or-later
|
||||
└── Makefile # Build automation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## updates.xml — Required in Repo Root
|
||||
|
||||
`updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
|
||||
|
||||
The `manifest.xml` must reference it via:
|
||||
```xml
|
||||
<updateservers>
|
||||
<server type="extension" priority="1" name="{{EXTENSION_NAME}}">
|
||||
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/main/updates.xml
|
||||
</server>
|
||||
</updateservers>
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
|
||||
- The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
|
||||
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
|
||||
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
|
||||
|
||||
---
|
||||
|
||||
## manifest.xml Rules
|
||||
|
||||
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
|
||||
- `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
|
||||
- Must include `<updateservers>` block pointing to this repo's `updates.xml`.
|
||||
- Must include `<files folder="site">` and `<administration>` sections.
|
||||
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
|
||||
|
||||
---
|
||||
|
||||
## GitHub Actions — Token Usage
|
||||
|
||||
Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
|
||||
|
||||
```yaml
|
||||
# ✅ Correct
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ❌ Wrong — never use these in workflows
|
||||
token: ${{ github.token }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MokoStandards Reference
|
||||
|
||||
This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). Authoritative policies:
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
|
||||
| [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
|
||||
| [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
|
||||
| [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
|
||||
| [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
|
||||
| [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
| Context | Convention | Example |
|
||||
|---------|-----------|---------|
|
||||
| PHP class | `PascalCase` | `MyController` |
|
||||
| PHP method / function | `camelCase` | `getItems()` |
|
||||
| PHP variable | `$snake_case` | `$item_id` |
|
||||
| PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
|
||||
| PHP class file | `PascalCase.php` | `ItemModel.php` |
|
||||
| YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
|
||||
| Markdown doc | `kebab-case.md` | `installation-guide.md` |
|
||||
|
||||
---
|
||||
|
||||
## Commit Messages
|
||||
|
||||
Format: `<type>(<scope>): <subject>` — imperative, lower-case subject, no trailing period.
|
||||
|
||||
Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build`
|
||||
|
||||
---
|
||||
|
||||
## Branch Naming
|
||||
|
||||
Format: `<prefix>/<MAJOR.MINOR.PATCH>[/description]`
|
||||
|
||||
Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/`
|
||||
|
||||
---
|
||||
|
||||
## Keeping Documentation Current
|
||||
|
||||
| Change type | Documentation to update |
|
||||
|-------------|------------------------|
|
||||
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
|
||||
| New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
|
||||
| New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
|
||||
| New or changed workflow | `docs/workflows/<workflow-name>.md` |
|
||||
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
|
||||
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
|
||||
|
||||
---
|
||||
|
||||
## Key Constraints
|
||||
|
||||
- Never commit directly to `main` — all changes go via PR, squash-merged
|
||||
- Never skip the FILE INFORMATION block on a new file
|
||||
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
|
||||
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
|
||||
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
|
||||
- Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync
|
||||
137
.github/copilot.yml
vendored
Normal file
137
.github/copilot.yml
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
# GitHub Copilot Configuration
|
||||
# This file configures GitHub Copilot settings for the repository
|
||||
|
||||
# Allowed domains for Copilot to access
|
||||
# These domains are trusted sources that Copilot can fetch information from
|
||||
allowed_domains:
|
||||
# Standard license providers
|
||||
- "www.gnu.org" # GNU licenses (GPL, LGPL, AGPL)
|
||||
- "opensource.org" # Open Source Initiative
|
||||
- "choosealicense.com" # GitHub's license chooser
|
||||
- "spdx.org" # Software Package Data Exchange
|
||||
- "creativecommons.org" # Creative Commons licenses
|
||||
- "apache.org" # Apache Software Foundation
|
||||
- "fsf.org" # Free Software Foundation
|
||||
|
||||
# Documentation and standards
|
||||
- "semver.org" # Semantic Versioning
|
||||
- "keepachangelog.com" # Changelog standards
|
||||
- "conventionalcommits.org" # Commit message standards
|
||||
|
||||
# GitHub and related
|
||||
- "github.com" # GitHub main site
|
||||
- "docs.github.com" # GitHub documentation
|
||||
- "raw.githubusercontent.com" # GitHub raw content
|
||||
|
||||
# Package managers and registries
|
||||
- "npmjs.com" # npm registry
|
||||
- "pypi.org" # Python Package Index
|
||||
- "packagist.org" # PHP Composer packages
|
||||
- "rubygems.org" # Ruby gems
|
||||
|
||||
# Standards and specifications
|
||||
- "json-schema.org" # JSON Schema
|
||||
- "w3.org" # W3C standards
|
||||
- "ietf.org" # IETF RFCs and standards
|
||||
|
||||
# PHP and Joomla specific
|
||||
- "joomla.org" # Joomla CMS
|
||||
- "docs.joomla.org" # Joomla documentation
|
||||
- "downloads.joomla.org" # Joomla core downloads
|
||||
- "php.net" # PHP documentation
|
||||
- "getcomposer.org" # Composer dependency manager
|
||||
- "packagist.org" # Composer package registry (also listed under packages)
|
||||
|
||||
# Dolibarr specific
|
||||
- "dolibarr.org" # Dolibarr ERP/CRM
|
||||
- "wiki.dolibarr.org" # Dolibarr wiki
|
||||
- "docs.dolibarr.org" # Dolibarr developer documentation
|
||||
|
||||
# Moko Consulting
|
||||
- "mokoconsulting.tech" # Moko Consulting main site
|
||||
- "*.mokoconsulting.tech" # All Moko Consulting subdomains (API, docs, CDN, etc.)
|
||||
|
||||
# Google services
|
||||
- "drive.google.com" # Google Drive (file sharing and assets)
|
||||
- "docs.google.com" # Google Docs
|
||||
- "sheets.google.com" # Google Sheets
|
||||
- "accounts.google.com" # Google authentication
|
||||
- "storage.googleapis.com" # Google Cloud Storage
|
||||
- "*.googleapis.com" # Google APIs (Maps, Fonts, etc.)
|
||||
- "*.googleusercontent.com" # Google user-uploaded content and CDN
|
||||
- "fonts.googleapis.com" # Google Fonts CSS
|
||||
- "fonts.gstatic.com" # Google Fonts static assets
|
||||
|
||||
# GitHub extended
|
||||
- "api.github.com" # GitHub REST API
|
||||
- "upload.github.com" # GitHub file uploads
|
||||
- "objects.githubusercontent.com" # GitHub release assets and LFS
|
||||
- "user-images.githubusercontent.com" # GitHub issue/PR image attachments
|
||||
- "codeload.github.com" # GitHub archive downloads
|
||||
- "ghcr.io" # GitHub Container Registry
|
||||
- "pkg.github.com" # GitHub Packages
|
||||
|
||||
# Developer reference
|
||||
- "developer.mozilla.org" # MDN Web Docs
|
||||
- "stackoverflow.com" # Stack Overflow
|
||||
- "git-scm.com" # Git documentation
|
||||
|
||||
# CDN and infrastructure
|
||||
- "cdn.jsdelivr.net" # jsDelivr CDN
|
||||
- "unpkg.com" # unpkg CDN
|
||||
- "cdnjs.cloudflare.com" # Cloudflare CDN
|
||||
- "img.shields.io" # Shields.io badge images
|
||||
- "shields.io" # Shields.io badge service
|
||||
|
||||
# Container registries
|
||||
- "hub.docker.com" # Docker Hub
|
||||
- "registry-1.docker.io" # Docker registry pulls
|
||||
- "index.docker.io" # Docker index
|
||||
|
||||
# CI / code quality
|
||||
- "codecov.io" # Code coverage reporting
|
||||
- "coveralls.io" # Coveralls coverage service
|
||||
- "sonarcloud.io" # SonarCloud static analysis
|
||||
|
||||
# Terraform / infrastructure
|
||||
- "registry.terraform.io" # Terraform provider registry
|
||||
- "releases.hashicorp.com" # HashiCorp release downloads
|
||||
- "checkpoint-api.hashicorp.com" # HashiCorp update checks
|
||||
|
||||
# Settings for code generation and suggestions
|
||||
copilot:
|
||||
# Enable Copilot for this repository
|
||||
enabled: true
|
||||
|
||||
# File patterns to include for Copilot suggestions
|
||||
include:
|
||||
- "**/*.py"
|
||||
- "**/*.js"
|
||||
- "**/*.php"
|
||||
- "**/*.md"
|
||||
- "**/*.yml"
|
||||
- "**/*.yaml"
|
||||
- "**/*.json"
|
||||
- "**/*.xml"
|
||||
- "**/*.sh"
|
||||
|
||||
# File patterns to exclude from Copilot suggestions
|
||||
exclude:
|
||||
- "**/node_modules/**"
|
||||
- "**/vendor/**"
|
||||
- "**/build/**"
|
||||
- "**/dist/**"
|
||||
- "**/.git/**"
|
||||
- "**/LICENSE"
|
||||
- "**/CHANGELOG.md"
|
||||
|
||||
# Notes:
|
||||
# ------
|
||||
# - This configuration allows GitHub Copilot to fetch information from trusted sources
|
||||
# - License providers are included to help with license text and compliance information
|
||||
# - Package registries help with dependency management and version checking
|
||||
# - Standards organizations provide authoritative specifications
|
||||
# - Platform-specific sites (Joomla, Dolibarr, PHP) support our technology stack
|
||||
# - All domains listed are well-known, reputable sources in their respective domains
|
||||
# - This list focuses on read-only access to public information
|
||||
# - No authentication credentials should be used with these domains
|
||||
107
.github/dependabot.yml
vendored
Normal file
107
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: GitHub.Dependabot
|
||||
# INGROUP: MokoStandards.Security
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.github/dependabot.yml
|
||||
# VERSION: 03.09.03
|
||||
# BRIEF: Dependabot configuration for automated dependency updates and security patches
|
||||
# NOTE: Monitors GitHub Actions for vulnerabilities and keeps ecosystem secure
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Monitor GitHub Actions for security updates
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 1
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "security"
|
||||
- "automated"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
include: "scope"
|
||||
reviewers:
|
||||
- "mokoconsulting-tech/maintainers"
|
||||
assignees:
|
||||
- "jmiller-moko"
|
||||
# Group all updates together
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
# Monitor Python dependencies for security updates
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 1
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "security"
|
||||
- "automated"
|
||||
- "python"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
include: "scope"
|
||||
reviewers:
|
||||
- "mokoconsulting-tech/maintainers"
|
||||
assignees:
|
||||
- "jmiller-moko"
|
||||
# Group all updates together
|
||||
groups:
|
||||
python-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
# Monitor npm dependencies for security updates
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 1
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "security"
|
||||
- "automated"
|
||||
- "javascript"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
include: "scope"
|
||||
reviewers:
|
||||
- "mokoconsulting-tech/maintainers"
|
||||
assignees:
|
||||
- "jmiller-moko"
|
||||
# Group all updates together
|
||||
groups:
|
||||
npm-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
# Monitor Composer dependencies for security updates
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 1
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "security"
|
||||
- "automated"
|
||||
- "php"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
include: "scope"
|
||||
reviewers:
|
||||
- "mokoconsulting-tech/maintainers"
|
||||
assignees:
|
||||
- "jmiller-moko"
|
||||
# Group all updates together
|
||||
groups:
|
||||
composer-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
20
.github/pull_request_template.md
vendored
Normal file
20
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Pull Request
|
||||
|
||||
## Purpose
|
||||
|
||||
## Change Summary
|
||||
|
||||
## Testing Evidence
|
||||
|
||||
## Risk and Rollback
|
||||
|
||||
## Checklist
|
||||
- [ ] Follows Conventional Commits
|
||||
- [ ] Tests added or updated
|
||||
- [ ] Documentation updated if required
|
||||
- [ ] License header present where applicable
|
||||
- [ ] Linked issue(s) referenced
|
||||
|
||||
## Reviewer Notes
|
||||
|
||||
## Review and Approval
|
||||
76
.github/workflows/auto-assign.yml
vendored
Normal file
76
.github/workflows/auto-assign.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Workflows.Shared
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.github/workflows/auto-assign.yml
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Auto-assign jmiller to unassigned issues and PRs every 15 minutes
|
||||
|
||||
name: Auto-Assign Issues & PRs
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
schedule:
|
||||
- cron: '0 */12 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
auto-assign:
|
||||
name: Assign unassigned issues and PRs
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Assign unassigned issues
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
REPO="${{ github.repository }}"
|
||||
ASSIGNEE="jmiller"
|
||||
|
||||
echo "## 🏷️ Auto-Assign Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
ASSIGNED_ISSUES=0
|
||||
ASSIGNED_PRS=0
|
||||
|
||||
# Assign unassigned open issues
|
||||
ISSUES=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?state=open&per_page=100&assignee=none" | jq -r '.[].number' 2>/dev/null || true)
|
||||
for NUM in $ISSUES; do
|
||||
# Skip PRs (the issues endpoint returns PRs too)
|
||||
IS_PR=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/$NUM" | jq -r '.pull_request // empty' 2>/dev/null || true)
|
||||
if [ -z "$IS_PR" ]; then
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/$NUM/assignees" 2>/dev/null -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && {
|
||||
ASSIGNED_ISSUES=$((ASSIGNED_ISSUES + 1))
|
||||
echo " Assigned issue #$NUM"
|
||||
} || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Assign unassigned open PRs
|
||||
PRS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/pulls?state=open&per_page=100" | jq -r '.[] | select(.assignees | length == 0) | .number' 2>/dev/null || true)
|
||||
for NUM in $PRS; do
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/$NUM/assignees" 2>/dev/null -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && {
|
||||
ASSIGNED_PRS=$((ASSIGNED_PRS + 1))
|
||||
echo " Assigned PR #$NUM"
|
||||
} || true
|
||||
done
|
||||
|
||||
echo "| Type | Assigned |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|----------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Issues | $ASSIGNED_ISSUES |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Pull Requests | $ASSIGNED_PRS |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "$ASSIGNED_ISSUES" -eq 0 ] && [ "$ASSIGNED_PRS" -eq 0 ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ All issues and PRs already have assignees" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
207
.github/workflows/auto-dev-issue.yml
vendored
Normal file
207
.github/workflows/auto-dev-issue.yml
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# This file is part of a Moko Consulting project.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/shared/auto-dev-issue.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Auto-create tracking issue with sub-issues for dev/rc branch workflow
|
||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos.
|
||||
|
||||
name: Dev/RC Branch Issue
|
||||
|
||||
on:
|
||||
# Auto-create on RC branch creation
|
||||
create:
|
||||
# Manual trigger for dev branches
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch name (e.g., dev/my-feature or dev/04.06)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
create-issue:
|
||||
name: Create version tracking issue
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
(github.event_name == 'workflow_dispatch') ||
|
||||
(github.event.ref_type == 'branch' &&
|
||||
(startsWith(github.event.ref, 'rc/') ||
|
||||
startsWith(github.event.ref, 'alpha/') ||
|
||||
startsWith(github.event.ref, 'beta/')))
|
||||
|
||||
steps:
|
||||
- name: Create tracking issue and sub-issues
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
# For manual dispatch, use input; for auto, use event ref
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
BRANCH="${{ inputs.branch }}"
|
||||
else
|
||||
BRANCH="${{ github.event.ref }}"
|
||||
fi
|
||||
REPO="${{ github.repository }}"
|
||||
ACTOR="${{ github.actor }}"
|
||||
NOW=$(date -u '+%Y-%m-%d %H:%M UTC')
|
||||
|
||||
# Determine branch type and version
|
||||
if [[ "$BRANCH" == rc/* ]]; then
|
||||
VERSION="${BRANCH#rc/}"
|
||||
BRANCH_TYPE="Release Candidate"
|
||||
LABEL_TYPE="type: release"
|
||||
TITLE_PREFIX="rc"
|
||||
elif [[ "$BRANCH" == beta/* ]]; then
|
||||
VERSION="${BRANCH#beta/}"
|
||||
BRANCH_TYPE="Beta"
|
||||
LABEL_TYPE="type: release"
|
||||
TITLE_PREFIX="beta"
|
||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||
VERSION="${BRANCH#alpha/}"
|
||||
BRANCH_TYPE="Alpha"
|
||||
LABEL_TYPE="type: release"
|
||||
TITLE_PREFIX="alpha"
|
||||
else
|
||||
VERSION="${BRANCH#dev/}"
|
||||
BRANCH_TYPE="Development"
|
||||
LABEL_TYPE="type: feature"
|
||||
TITLE_PREFIX="feat"
|
||||
fi
|
||||
|
||||
TITLE="${TITLE_PREFIX}(${VERSION}): ${BRANCH_TYPE} tracking for ${BRANCH}"
|
||||
|
||||
# Check for existing issue with same title prefix
|
||||
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?state=open&per_page=10" 2>/dev/null \
|
||||
| jq -r ".[] | select(.title | startswith(\"${TITLE_PREFIX}(${VERSION})\")) | .number" 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "ℹ️ Issue #${EXISTING} already exists for ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Define sub-issues for the workflow ─────────────────────────
|
||||
if [[ "$BRANCH" == rc/* ]]; then
|
||||
SUB_ISSUES=(
|
||||
"RC Testing|Verify all features work on rc branch|type: test,release-candidate"
|
||||
"Regression Testing|Run full regression suite before merge|type: test,release-candidate"
|
||||
"Version Bump|Bump version in README.md and all headers|type: version,release-candidate"
|
||||
"Changelog Update|Update CHANGELOG.md with release notes|documentation,release-candidate"
|
||||
"Merge to Version Branch|Create PR to version/XX|type: release,needs-review"
|
||||
)
|
||||
elif [[ "$BRANCH" == alpha/* ]] || [[ "$BRANCH" == beta/* ]]; then
|
||||
SUB_ISSUES=(
|
||||
"Testing|Verify features on ${BRANCH_TYPE} branch|type: test,status: in-progress"
|
||||
"Bug Fixes|Fix issues found during ${BRANCH_TYPE} testing|type: bug,status: pending"
|
||||
"Promote to Next Stage|Create PR to promote to next release stage|type: release,needs-review"
|
||||
)
|
||||
else
|
||||
SUB_ISSUES=(
|
||||
"Development|Implement feature/fix on dev branch|type: feature,status: in-progress"
|
||||
"Unit Testing|Write and pass unit tests|type: test,status: pending"
|
||||
"Code Review|Request and complete code review|needs-review,status: pending"
|
||||
"Version Bump|Bump version in README.md and all headers|type: version,status: pending"
|
||||
"Changelog Update|Update CHANGELOG.md with release notes|documentation,status: pending"
|
||||
"Create RC Branch|Promote dev to rc branch for final testing|type: release,status: pending"
|
||||
"Merge to Main|Create PR from rc/dev to main|type: release,needs-review,status: pending"
|
||||
)
|
||||
fi
|
||||
|
||||
# ── Create sub-issues first ───────────────────────────────────────
|
||||
SUB_LIST=""
|
||||
SUB_NUMBERS=""
|
||||
for SUB in "${SUB_ISSUES[@]}"; do
|
||||
IFS='|' read -r SUB_TITLE SUB_DESC SUB_LABELS <<< "$SUB"
|
||||
SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}"
|
||||
|
||||
SUB_BODY=$(printf '### %s\n\n%s\n\n| Field | Value |\n|-------|-------|\n| **Parent Branch** | `%s` |\n| **Version** | `%s` |\n\n---\n*Sub-issue of the %s tracking issue for `%s`.*' \
|
||||
"$SUB_TITLE" "$SUB_DESC" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$BRANCH")
|
||||
|
||||
SUB_URL=$(gh issue create \
|
||||
--repo "$REPO" \
|
||||
--title "$SUB_FULL_TITLE" \
|
||||
--body "$SUB_BODY" \
|
||||
--label "${SUB_LABELS}" \
|
||||
--assignee "jmiller" 2>&1)
|
||||
|
||||
SUB_NUM=$(echo "$SUB_URL" | grep -oE '[0-9]+$')
|
||||
if [ -n "$SUB_NUM" ]; then
|
||||
SUB_LIST="${SUB_LIST}\n- [ ] ${SUB_TITLE} (#${SUB_NUM})"
|
||||
SUB_NUMBERS="${SUB_NUMBERS} #${SUB_NUM}"
|
||||
fi
|
||||
sleep 0.3
|
||||
done
|
||||
|
||||
# ── Create parent tracking issue ──────────────────────────────────
|
||||
PARENT_BODY=$(printf '## %s Branch Created\n\n| Field | Value |\n|-------|-------|\n| **Branch** | `%s` |\n| **Version** | `%s` |\n| **Type** | %s |\n| **Created by** | @%s |\n| **Created at** | %s |\n| **Repository** | `%s` |\n\n## Workflow Sub-Issues\n\n%b\n\n---\n*Auto-created by [auto-dev-issue.yml](.github/workflows/auto-dev-issue.yml) on branch creation.*' \
|
||||
"$BRANCH_TYPE" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$ACTOR" "$NOW" "$REPO" "$SUB_LIST")
|
||||
|
||||
PARENT_URL=$(gh issue create \
|
||||
--repo "$REPO" \
|
||||
--title "$TITLE" \
|
||||
--body "$PARENT_BODY" \
|
||||
--label "${LABEL_TYPE},version" \
|
||||
--assignee "jmiller" 2>&1)
|
||||
|
||||
PARENT_NUM=$(echo "$PARENT_URL" | grep -oE '[0-9]+$')
|
||||
|
||||
# ── Link sub-issues back to parent ────────────────────────────────
|
||||
if [ -n "$PARENT_NUM" ]; then
|
||||
for SUB in "${SUB_ISSUES[@]}"; do
|
||||
IFS='|' read -r SUB_TITLE _ _ <<< "$SUB"
|
||||
SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}"
|
||||
SUB_NUM=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?state=open&per_page=20" 2>/dev/null \
|
||||
| jq -r ".[] | select(.title == \"${SUB_FULL_TITLE}\") | .number" 2>/dev/null | head -1)
|
||||
if [ -n "$SUB_NUM" ]; then
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/${SUB_NUM}" 2>/dev/null -X PATCH \
|
||||
-f body="$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/${SUB_NUM}" | jq -r '.body' 2>/dev/null)
|
||||
|
||||
> **Parent Issue:** #${PARENT_NUM}" --silent 2>/dev/null || true
|
||||
fi
|
||||
sleep 0.2
|
||||
done
|
||||
fi
|
||||
|
||||
# ── Create or update prerelease for alpha/beta/rc ────────────────
|
||||
if [[ "$BRANCH" == rc/* ]] || [[ "$BRANCH" == alpha/* ]] || [[ "$BRANCH" == beta/* ]]; then
|
||||
case "$BRANCH_TYPE" in
|
||||
Alpha) RELEASE_TAG="alpha" ;;
|
||||
Beta) RELEASE_TAG="beta" ;;
|
||||
"Release Candidate") RELEASE_TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
||||
if [ -z "$EXISTING" ]; then
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--title "${RELEASE_TAG} (${VERSION})" \
|
||||
--notes "## ${BRANCH_TYPE} ${VERSION}\n\nBranch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \
|
||||
--prerelease \
|
||||
--target main 2>/dev/null || true
|
||||
echo "${BRANCH_TYPE} release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--title "${RELEASE_TAG} (${VERSION})" --prerelease 2>/dev/null || true
|
||||
echo "${BRANCH_TYPE} release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────
|
||||
echo "## Dev Workflow Issues Created" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Item | Issue |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| **Parent** | ${PARENT_URL} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| **Sub-issues** |${SUB_NUMBERS} |" >> $GITHUB_STEP_SUMMARY
|
||||
563
.github/workflows/auto-release.yml
vendored
Normal file
563
.github/workflows/auto-release.yml
vendored
Normal file
@@ -0,0 +1,563 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/joomla/auto-release.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum
|
||||
#
|
||||
# +========================================================================+
|
||||
# | BUILD & RELEASE PIPELINE (JOOMLA) |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Triggers on push to main (skips bot commits + [skip ci]): |
|
||||
# | |
|
||||
# | Every push: |
|
||||
# | 1. Read version from README.md |
|
||||
# | 3. Set platform version (Joomla <version>) |
|
||||
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
|
||||
# | 5. Write updates.xml (Joomla update server XML) |
|
||||
# | 6. Create git tag vXX.YY.ZZ |
|
||||
# | 7a. Patch: update existing GitHub Release for this minor |
|
||||
# | 8. Build ZIP, upload asset, write SHA-256 to updates.xml |
|
||||
# | |
|
||||
# | Every version change: archives main -> version/XX.YY branch |
|
||||
# | Patch 00 = development (no release). First release = patch 01. |
|
||||
# | First release only (patch == 01): |
|
||||
# | 7b. Create new GitHub Release |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: Build & Release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build & Release Pipeline
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.GA_TOKEN || github.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set authenticated push URL
|
||||
run: git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
- name: Setup MokoStandards tools
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
git clone --depth 1 --branch version/04 --quiet \
|
||||
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards-API.git" \
|
||||
/tmp/mokostandards-api
|
||||
cd /tmp/mokostandards-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
|
||||
# -- STEP 1: Read version -----------------------------------------------
|
||||
- name: "Step 1: Read version from README.md"
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null)
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "No VERSION in README.md — skipping release"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
# Derive major.minor for branch naming (patches update existing branch)
|
||||
MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
|
||||
PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
|
||||
|
||||
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
||||
MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}')
|
||||
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
||||
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||
if [ "$PATCH" = "00" ]; then
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION (patch 00 = development — skipping release)"
|
||||
else
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
if [ "$PATCH" = "01" ]; then
|
||||
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION (first release — full pipeline)"
|
||||
else
|
||||
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION (patch — platform version + badges only)"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Check if already released
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
id: check
|
||||
run: |
|
||||
TAG="${{ steps.version.outputs.release_tag }}"
|
||||
BRANCH="${{ steps.version.outputs.branch }}"
|
||||
|
||||
TAG_EXISTS=false
|
||||
BRANCH_EXISTS=false
|
||||
|
||||
git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true
|
||||
git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q "$BRANCH" && BRANCH_EXISTS=true
|
||||
|
||||
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
||||
echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then
|
||||
echo "already_released=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# -- SANITY CHECKS -------------------------------------------------------
|
||||
- name: "Sanity: Pre-release validation"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
ERRORS=0
|
||||
|
||||
echo "## Pre-Release Sanity Checks (Joomla)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- Version drift check (must pass before release) --------
|
||||
README_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
|
||||
if [ "$README_VER" != "$VERSION" ]; then
|
||||
echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# Check CHANGELOG version matches
|
||||
CL_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' CHANGELOG.md 2>/dev/null | head -1)
|
||||
if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
|
||||
echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
fi
|
||||
|
||||
# Check composer.json version if present
|
||||
if [ -f "composer.json" ]; then
|
||||
COMP_VER=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' composer.json 2>/dev/null | head -1)
|
||||
if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
|
||||
echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Common checks
|
||||
if [ ! -f "LICENSE" ]; then
|
||||
echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
|
||||
echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# -- Joomla: manifest version drift --------
|
||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
if [ -n "$MANIFEST" ]; then
|
||||
XML_VER=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
||||
if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then
|
||||
echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
# -- Joomla: XML manifest existence --------
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "- No Joomla XML manifest found" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "- Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- Joomla: extension type check --------
|
||||
TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null)
|
||||
echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "$ERRORS" -gt 0 ]; then
|
||||
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
|
||||
# Always runs — every version change on main archives to version/XX.YY
|
||||
- name: "Step 2: Version archive branch"
|
||||
if: steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
BRANCH="${{ steps.version.outputs.branch }}"
|
||||
IS_MINOR="${{ steps.version.outputs.is_minor }}"
|
||||
PATCH="${{ steps.version.outputs.version }}"
|
||||
PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}')
|
||||
|
||||
# Check if branch exists
|
||||
if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
|
||||
git push origin HEAD:"$BRANCH" --force
|
||||
echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
|
||||
git push origin "$BRANCH" --force
|
||||
echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# -- STEP 3: Set platform version ----------------------------------------
|
||||
- name: "Step 3: Set platform version"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
php /tmp/mokostandards-api/cli/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch main
|
||||
|
||||
# -- STEP 4: Update version badges ----------------------------------------
|
||||
- name: "Step 4: Update version badges"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do
|
||||
if grep -q '\[VERSION:' "$f" 2>/dev/null; then
|
||||
sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f"
|
||||
fi
|
||||
done
|
||||
|
||||
# -- STEP 5: Write updates.xml (Joomla update server) ---------------------
|
||||
- name: "Step 5: Write updates.xml"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
# -- Parse extension metadata from XML manifest ----------------
|
||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "Warning: No Joomla XML manifest found — skipping updates.xml" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract fields using sed (portable — no grep -P)
|
||||
EXT_NAME=$(sed -n 's/.*<name>\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_CLIENT=$(sed -n 's/.*<extension[^>]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
TARGET_PLATFORM=$(sed -n 's/.*\(<targetplatform[^/]*\/>\).*/\1/p' "$MANIFEST" | head -1)
|
||||
PHP_MINIMUM=$(sed -n 's/.*<php_minimum>\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1)
|
||||
|
||||
# Fallbacks
|
||||
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
||||
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
||||
|
||||
# Templates/modules don't have <element> — derive from <name> (lowercased)
|
||||
if [ -z "$EXT_ELEMENT" ]; then
|
||||
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
|
||||
fi
|
||||
|
||||
# Build client tag: plugins and frontend modules need <client>site</client>
|
||||
CLIENT_TAG=""
|
||||
if [ -n "$EXT_CLIENT" ]; then
|
||||
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
||||
elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then
|
||||
CLIENT_TAG="<client>site</client>"
|
||||
fi
|
||||
|
||||
# Build folder tag for plugins (required for Joomla to match the update)
|
||||
FOLDER_TAG=""
|
||||
if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then
|
||||
FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
|
||||
fi
|
||||
|
||||
# Build targetplatform (fallback to Joomla 5 if not in manifest)
|
||||
if [ -z "$TARGET_PLATFORM" ]; then
|
||||
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
||||
fi
|
||||
|
||||
# Build php_minimum tag
|
||||
PHP_TAG=""
|
||||
if [ -n "$PHP_MINIMUM" ]; then
|
||||
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
||||
fi
|
||||
|
||||
DOWNLOAD_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
|
||||
INFO_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/tag/v${VERSION}"
|
||||
|
||||
# -- Build stable entry to temp file ─────────────────────────
|
||||
{
|
||||
printf '%s\n' ' <update>'
|
||||
printf '%s\n' " <name>${EXT_NAME}</name>"
|
||||
printf '%s\n' " <description>${EXT_NAME} update</description>"
|
||||
printf '%s\n' " <element>${EXT_ELEMENT}</element>"
|
||||
printf '%s\n' " <type>${EXT_TYPE}</type>"
|
||||
printf '%s\n' " <version>${VERSION}</version>"
|
||||
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
|
||||
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
|
||||
printf '%s\n' ' <tags>'
|
||||
printf '%s\n' ' <tag>stable</tag>'
|
||||
printf '%s\n' ' </tags>'
|
||||
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
|
||||
printf '%s\n' ' <downloads>'
|
||||
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
|
||||
printf '%s\n' ' </downloads>'
|
||||
printf '%s\n' " ${TARGET_PLATFORM}"
|
||||
[ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}"
|
||||
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
|
||||
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
|
||||
printf '%s\n' ' </update>'
|
||||
} > /tmp/stable_entry.xml
|
||||
|
||||
# -- Write updates.xml preserving dev/rc entries ──────────────
|
||||
# Extract existing entries for other stability levels
|
||||
# Order reflects release workflow: development → alpha → beta → rc → stable
|
||||
if [ -f "updates.xml" ]; then
|
||||
printf 'import re, sys\n' > /tmp/extract.py
|
||||
printf 'with open("updates.xml") as f: c = f.read()\n' >> /tmp/extract.py
|
||||
printf 'tag = sys.argv[1]\n' >> /tmp/extract.py
|
||||
printf 'm = re.search(r"( <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)", c, re.DOTALL)\n' >> /tmp/extract.py
|
||||
printf 'if m: print(m.group(1))\n' >> /tmp/extract.py
|
||||
fi
|
||||
DEV_ENTRY=$(python3 /tmp/extract.py development 2>/dev/null || true)
|
||||
ALPHA_ENTRY=$(python3 /tmp/extract.py alpha 2>/dev/null || true)
|
||||
BETA_ENTRY=$(python3 /tmp/extract.py beta 2>/dev/null || true)
|
||||
RC_ENTRY=$(python3 /tmp/extract.py rc 2>/dev/null || true)
|
||||
|
||||
{
|
||||
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
||||
printf '%s\n' '<updates>'
|
||||
[ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY"
|
||||
[ -n "$ALPHA_ENTRY" ] && echo "$ALPHA_ENTRY"
|
||||
[ -n "$BETA_ENTRY" ] && echo "$BETA_ENTRY"
|
||||
[ -n "$RC_ENTRY" ] && echo "$RC_ENTRY"
|
||||
cat /tmp/stable_entry.xml
|
||||
printf '%s\n' '</updates>'
|
||||
} > updates.xml
|
||||
|
||||
echo "updates.xml: ${VERSION} (stable + rc/dev preserved)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- Commit all changes ---------------------------------------------------
|
||||
- name: Commit release changes
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
if git diff --quiet && git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add -A
|
||||
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
|
||||
# -- STEP 6: Create tag ---------------------------------------------------
|
||||
- name: "Step 6: Create git tag"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.tag_exists != 'true' &&
|
||||
steps.version.outputs.is_minor == 'true'
|
||||
run: |
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
# Only create the major release tag if it doesn't exist yet
|
||||
if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
|
||||
git tag "$RELEASE_TAG"
|
||||
git push origin "$RELEASE_TAG"
|
||||
echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- STEP 7: Create or update GitHub Release ------------------------------
|
||||
- name: "Step 7: GitHub Release"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.tag_exists != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
BRANCH="${{ steps.version.outputs.branch }}"
|
||||
MAJOR="${{ steps.version.outputs.major }}"
|
||||
|
||||
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||
echo "$NOTES" > /tmp/release_notes.md
|
||||
|
||||
# Check if the major release already exists
|
||||
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
||||
|
||||
if [ -z "$EXISTING" ]; then
|
||||
# First release for this major
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||
--notes-file /tmp/release_notes.md \
|
||||
--target "$BRANCH"
|
||||
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
# Append version notes to existing major release
|
||||
CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true)
|
||||
{
|
||||
echo "$CURRENT_NOTES"
|
||||
echo ""
|
||||
echo "---"
|
||||
echo "### ${VERSION}"
|
||||
echo ""
|
||||
cat /tmp/release_notes.md
|
||||
} > /tmp/updated_notes.md
|
||||
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||
--notes-file /tmp/updated_notes.md
|
||||
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
|
||||
# Every patch builds an install-ready ZIP and uploads it to the minor release.
|
||||
# Result: one Release per minor version with a ZIP for each patch.
|
||||
- name: "Step 8: Build Joomla package and update checksum"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
# All ZIPs upload to the major release tag (vXX)
|
||||
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || {
|
||||
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Find extension element name from manifest
|
||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
||||
[ -z "$MANIFEST" ] && exit 0
|
||||
|
||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
|
||||
ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||
TAR_NAME="${EXT_ELEMENT}-${VERSION}.tar.gz"
|
||||
|
||||
# -- Build install packages from src/ ----------------------------
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — skipping package"; exit 0; }
|
||||
|
||||
EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*"
|
||||
|
||||
# ZIP package
|
||||
cd "$SOURCE_DIR"
|
||||
zip -r "/tmp/${ZIP_NAME}" . -x $EXCLUDES
|
||||
cd ..
|
||||
|
||||
# tar.gz package
|
||||
tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \
|
||||
--exclude='.ftpignore' --exclude='sftp-config*' \
|
||||
--exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
|
||||
|
||||
ZIP_SIZE=$(stat -c%s "/tmp/${ZIP_NAME}" 2>/dev/null || stat -f%z "/tmp/${ZIP_NAME}" 2>/dev/null || echo "unknown")
|
||||
TAR_SIZE=$(stat -c%s "/tmp/${TAR_NAME}" 2>/dev/null || stat -f%z "/tmp/${TAR_NAME}" 2>/dev/null || echo "unknown")
|
||||
|
||||
# -- Calculate SHA-256 for both ----------------------------------
|
||||
SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
|
||||
SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
|
||||
|
||||
# -- Upload both to release tag ----------------------------------
|
||||
gh release upload "$RELEASE_TAG" "/tmp/${ZIP_NAME}" --clobber 2>/dev/null || true
|
||||
gh release upload "$RELEASE_TAG" "/tmp/${TAR_NAME}" --clobber 2>/dev/null || true
|
||||
|
||||
# -- Update updates.xml with both download formats ---------------
|
||||
if [ -f "updates.xml" ]; then
|
||||
ZIP_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${ZIP_NAME}"
|
||||
TAR_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${TAR_NAME}"
|
||||
|
||||
# Replace downloads block with both formats + SHA
|
||||
sed -i "s|<downloads>.*</downloads>|<downloads>\n <downloadurl type=\"full\" format=\"zip\">${ZIP_URL}</downloadurl>\n <downloadurl type=\"full\" format=\"tar.gz\">${TAR_URL}</downloadurl>\n </downloads>|" updates.xml 2>/dev/null || true
|
||||
if grep -q '<sha256>' updates.xml; then
|
||||
sed -i "s|<sha256>.*</sha256>|<sha256>${SHA256_ZIP}</sha256>|" updates.xml
|
||||
else
|
||||
sed -i "s|</downloads>|</downloads>\n <sha256>${SHA256_ZIP}</sha256>|" updates.xml
|
||||
fi
|
||||
|
||||
git add updates.xml
|
||||
git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" || true
|
||||
git push || true
|
||||
fi
|
||||
|
||||
echo "### Joomla Packages" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | Size | SHA-256 |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|---------|------|---------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Download | [${PACKAGE_NAME}](https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- Summary --------------------------------------------------------------
|
||||
- name: Pipeline Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Build & Release Complete (Joomla)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | [View](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
144
.github/workflows/auto-update-sha.yml
vendored
Normal file
144
.github/workflows/auto-update-sha.yml
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoOnyx.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
# PATH: /.github/workflows/auto-update-sha.yml
|
||||
# VERSION: 03.09.03
|
||||
# BRIEF: Automatically update SHA-256 hash in updates.xml after release
|
||||
# NOTE: Ensures updates.xml stays synchronized with release packages
|
||||
|
||||
name: Auto-Update SHA Hash
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to update SHA for (e.g., 03.08.03)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-sha:
|
||||
name: Update SHA-256 Hash in updates.xml
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
|
||||
- name: Get release tag
|
||||
id: tag
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
TAG="${{ inputs.tag }}"
|
||||
else
|
||||
TAG="${{ github.event.release.tag_name }}"
|
||||
fi
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "Processing release: ${TAG}"
|
||||
|
||||
- name: Download release package
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
PACKAGE_NAME="mokoonyx-src-${TAG}.zip"
|
||||
DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${PACKAGE_NAME}"
|
||||
|
||||
echo "Downloading: ${DOWNLOAD_URL}"
|
||||
curl -L -o "${PACKAGE_NAME}" "${DOWNLOAD_URL}"
|
||||
|
||||
if [ ! -f "${PACKAGE_NAME}" ]; then
|
||||
echo "Error: Failed to download package"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PACKAGE_NAME=${PACKAGE_NAME}" >> $GITHUB_ENV
|
||||
|
||||
- name: Calculate SHA-256 hash
|
||||
id: sha
|
||||
run: |
|
||||
SHA256_HASH=$(sha256sum "${PACKAGE_NAME}" | cut -d' ' -f1)
|
||||
echo "sha256=${SHA256_HASH}" >> $GITHUB_OUTPUT
|
||||
echo "SHA-256 Hash: ${SHA256_HASH}"
|
||||
|
||||
- name: Update updates.xml
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
SHA256="${{ steps.sha.outputs.sha256 }}"
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
|
||||
# Update version
|
||||
sed -i "s|<version>.*</version>|<version>${TAG}</version>|" updates.xml
|
||||
|
||||
# Update creation date
|
||||
sed -i "s|<creationDate>.*</creationDate>|<creationDate>${DATE}</creationDate>|" updates.xml
|
||||
|
||||
# Update download URL
|
||||
sed -i "s|<downloadurl type='full' format='zip'>.*</downloadurl>|<downloadurl type='full' format='zip'>https://github.com/${{ github.repository }}/releases/download/${TAG}/mokoonyx-src-${TAG}.zip</downloadurl>|" updates.xml
|
||||
|
||||
# Update or add SHA-256 hash
|
||||
if grep -q "<sha256>" updates.xml; then
|
||||
sed -i "s|<sha256>.*</sha256>|<sha256>sha256:${SHA256}</sha256>|" updates.xml
|
||||
else
|
||||
# Add SHA-256 after downloadurl
|
||||
sed -i "/<\/downloadurl>/a\ <sha256>sha256:${SHA256}<\/sha256>" updates.xml
|
||||
fi
|
||||
|
||||
echo "Updated updates.xml with:"
|
||||
echo " Version: ${TAG}"
|
||||
echo " Date: ${DATE}"
|
||||
echo " SHA-256: ${SHA256}"
|
||||
|
||||
- name: Check for changes
|
||||
id: changes
|
||||
run: |
|
||||
if git diff --quiet updates.xml; then
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "No changes to updates.xml"
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "Changes detected in updates.xml"
|
||||
git diff updates.xml
|
||||
fi
|
||||
|
||||
- name: Commit and push changes
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
|
||||
git add updates.xml
|
||||
git commit -m "chore: Update SHA-256 hash for release ${TAG} - SHA: ${{ steps.sha.outputs.sha256 }}"
|
||||
|
||||
git push origin main
|
||||
|
||||
echo "Successfully updated updates.xml with SHA-256 hash for release ${TAG}"
|
||||
|
||||
- name: Summary
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
echo "### SHA-256 Hash Updated Successfully" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Release: ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- SHA-256: \`${{ steps.sha.outputs.sha256 }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- File: updates.xml" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The Joomla update server will now provide the correct package hash." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Summary (no changes)
|
||||
if: steps.changes.outputs.has_changes == 'false'
|
||||
run: |
|
||||
echo "### No Updates Needed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "updates.xml already contains the correct SHA-256 hash for release ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
|
||||
114
.github/workflows/branch-freeze.yml
vendored
Normal file
114
.github/workflows/branch-freeze.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/shared/branch-freeze.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Freeze or unfreeze any branch via ruleset — manual workflow_dispatch
|
||||
|
||||
name: Branch Freeze
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch to freeze/unfreeze (e.g., version/04, dev/feature)'
|
||||
required: true
|
||||
type: string
|
||||
action:
|
||||
description: 'Action to perform'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- freeze
|
||||
- unfreeze
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
manage-freeze:
|
||||
name: "${{ inputs.action }} branch: ${{ inputs.branch }}"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check permissions
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
ACTOR="${{ github.actor }}"
|
||||
REPO="${{ github.repository }}"
|
||||
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \
|
||||
2>/dev/null | jq -r '.permission' || echo "read")
|
||||
if [ "$PERMISSION" != "admin" ]; then
|
||||
echo "Denied: only admins can freeze/unfreeze branches (${ACTOR} has ${PERMISSION})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: "${{ inputs.action }} branch"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
BRANCH="${{ inputs.branch }}"
|
||||
ACTION="${{ inputs.action }}"
|
||||
REPO="${{ github.repository }}"
|
||||
RULESET_NAME="FROZEN: ${BRANCH}"
|
||||
|
||||
echo "## Branch Freeze" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "$ACTION" = "freeze" ]; then
|
||||
# Check if ruleset already exists
|
||||
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/rulesets" 2>/dev/null \
|
||||
| jq -r ".[] | select(.name == \"${RULESET_NAME}\") | .id" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "Branch \`${BRANCH}\` is already frozen (ruleset #${EXISTING})" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create freeze ruleset — blocks all updates except admin bypass
|
||||
printf '{"name":"%s","target":"branch","enforcement":"active",' "${RULESET_NAME}" > /tmp/ruleset.json
|
||||
printf '"bypass_actors":[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}],' >> /tmp/ruleset.json
|
||||
printf '"conditions":{"ref_name":{"include":["refs/heads/%s"],"exclude":[]}},' "${BRANCH}" >> /tmp/ruleset.json
|
||||
printf '"rules":[{"type":"update"},{"type":"deletion"},{"type":"non_fast_forward"}]}' >> /tmp/ruleset.json
|
||||
|
||||
RESULT=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/rulesets" 2>/dev/null -X POST -d @/tmp/ruleset.json 2>&1 | jq -r '.id') || true
|
||||
|
||||
if echo "$RESULT" | grep -qE '^[0-9]+$'; then
|
||||
echo "Frozen \`${BRANCH}\` — ruleset #${RESULT}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branch | \`${BRANCH}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Ruleset | #${RESULT} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Rules | No updates, no deletion, no force push |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Bypass | Repository admins only |" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Failed to freeze: ${RESULT}" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
elif [ "$ACTION" = "unfreeze" ]; then
|
||||
# Find and delete the freeze ruleset
|
||||
RULESET_ID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/rulesets" 2>/dev/null \
|
||||
| jq -r ".[] | select(.name == \"${RULESET_NAME}\") | .id" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$RULESET_ID" ]; then
|
||||
echo "Branch \`${BRANCH}\` is not frozen (no ruleset found)" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/rulesets/${RULESET_ID}" 2>/dev/null -X DELETE --silent 2>/dev/null
|
||||
|
||||
echo "Unfrozen \`${BRANCH}\` — ruleset #${RULESET_ID} deleted" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
rm -f /tmp/ruleset.json
|
||||
99
.github/workflows/changelog-validation.yml
vendored
Normal file
99
.github/workflows/changelog-validation.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# This file is part of a Moko Consulting project.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow.Template
|
||||
# INGROUP: MokoStandards.CI
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/shared/changelog-validation.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Validates CHANGELOG.md format and version consistency
|
||||
# NOTE: Deployed to .github/workflows/changelog-validation.yml in governed repos.
|
||||
|
||||
name: Changelog Validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'dev/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
validate-changelog:
|
||||
name: Validate CHANGELOG.md
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Check CHANGELOG.md exists
|
||||
run: |
|
||||
echo "### Changelog Validation" >> $GITHUB_STEP_SUMMARY
|
||||
if [ ! -f "CHANGELOG.md" ]; then
|
||||
echo "CHANGELOG.md not found in repository root." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
echo "CHANGELOG.md exists." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check VERSION header matches README.md
|
||||
run: |
|
||||
# Extract version from README.md FILE INFORMATION block
|
||||
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
|
||||
if [ -z "$README_VERSION" ]; then
|
||||
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that CHANGELOG.md has a matching version header
|
||||
CHANGELOG_VERSION=$(grep -oP '^\#\#\s*\[\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' CHANGELOG.md | head -1)
|
||||
if [ -z "$CHANGELOG_VERSION" ]; then
|
||||
echo "No version header found in CHANGELOG.md (expected \`## [XX.YY.ZZ] - YYYY-MM-DD\`)." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$CHANGELOG_VERSION" != "$README_VERSION" ]; then
|
||||
echo "CHANGELOG latest version \`${CHANGELOG_VERSION}\` does not match README VERSION \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "CHANGELOG version \`${CHANGELOG_VERSION}\` matches README VERSION." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Validate conventional changelog format
|
||||
run: |
|
||||
ERRORS=0
|
||||
|
||||
# Check that version entries follow ## [XX.YY.ZZ] - YYYY-MM-DD format
|
||||
while IFS= read -r LINE; do
|
||||
if ! echo "$LINE" | grep -qP '^\#\#\s*\[[0-9]{2}\.[0-9]{2}\.[0-9]{2}\]\s*-\s*[0-9]{4}-[0-9]{2}-[0-9]{2}'; then
|
||||
echo "Malformed version header: \`${LINE}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo " Expected format: \`## [XX.YY.ZZ] - YYYY-MM-DD\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done < <(grep -P '^\#\#\s*\[' CHANGELOG.md)
|
||||
|
||||
ENTRY_COUNT=$(grep -cP '^\#\#\s*\[' CHANGELOG.md || echo "0")
|
||||
if [ "$ENTRY_COUNT" -eq 0 ]; then
|
||||
echo "No version entries found in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "Found ${ENTRY_COUNT} version entr(ies) in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${ERRORS}" -gt 0 ]; then
|
||||
echo "**${ERRORS} format issue(s) found.**" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
else
|
||||
echo "**Changelog format validation passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
376
.github/workflows/ci-joomla.yml
vendored
Normal file
376
.github/workflows/ci-joomla.yml
vendored
Normal file
@@ -0,0 +1,376 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# This file is part of a Moko Consulting project.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow.Template
|
||||
# INGROUP: MokoStandards.CI
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/joomla/ci-joomla.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: CI workflow for Joomla extensions — lint, validate, test
|
||||
# NOTE: Deployed to .github/workflows/ci-joomla.yml in governed Joomla extension repos.
|
||||
|
||||
name: Joomla Extension CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'dev/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
lint-and-validate:
|
||||
name: Lint & Validate
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup PHP
|
||||
run: |
|
||||
php -v && composer --version
|
||||
|
||||
- name: Clone MokoStandards
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
git clone --depth 1 --branch version/04 --quiet \
|
||||
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards.git" \
|
||||
/tmp/mokostandards
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
if [ -f "composer.json" ]; then
|
||||
composer install \
|
||||
--no-interaction \
|
||||
--prefer-dist \
|
||||
--optimize-autoloader
|
||||
else
|
||||
echo "No composer.json found — skipping dependency install"
|
||||
fi
|
||||
|
||||
- name: PHP syntax check
|
||||
run: |
|
||||
ERRORS=0
|
||||
for DIR in src/ htdocs/; do
|
||||
if [ -d "$DIR" ]; then
|
||||
FOUND=1
|
||||
while IFS= read -r -d '' FILE; do
|
||||
OUTPUT=$(php -l "$FILE" 2>&1)
|
||||
if echo "$OUTPUT" | grep -q "Parse error"; then
|
||||
echo "::error file=${FILE}::${OUTPUT}"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done < <(find "$DIR" -name "*.php" -print0)
|
||||
fi
|
||||
done
|
||||
echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${ERRORS}" -gt 0 ]; then
|
||||
echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
else
|
||||
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: XML manifest validation
|
||||
run: |
|
||||
echo "### XML Manifest Validation" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=0
|
||||
|
||||
# Find the extension manifest (XML with <extension tag)
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No Joomla extension manifest found (XML file with \`<extension\` tag)." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "Manifest found: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Validate well-formed XML
|
||||
php -r "
|
||||
\$xml = @simplexml_load_file('$MANIFEST');
|
||||
if (\$xml === false) {
|
||||
echo 'INVALID';
|
||||
exit(1);
|
||||
}
|
||||
echo 'VALID';
|
||||
" > /tmp/xml_result 2>&1
|
||||
XML_RESULT=$(cat /tmp/xml_result)
|
||||
if [ "$XML_RESULT" != "VALID" ]; then
|
||||
echo "Manifest is not well-formed XML." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# Check required tags: name, version, author, namespace (Joomla 5+)
|
||||
for TAG in name version author namespace; do
|
||||
if ! grep -q "<${TAG}>" "$MANIFEST" 2>/dev/null; then
|
||||
echo "Missing required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "Found required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "${ERRORS}" -gt 0 ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**${ERRORS} manifest issue(s) found.**" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Check language files referenced in manifest
|
||||
run: |
|
||||
echo "### Language File Check" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=0
|
||||
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$MANIFEST" ]; then
|
||||
# Extract language file references from manifest
|
||||
LANG_FILES=$(grep -oP 'language\s+tag="[^"]*"[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
|
||||
if [ -z "$LANG_FILES" ]; then
|
||||
echo "No language file references found in manifest — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
while IFS= read -r LANG_FILE; do
|
||||
LANG_FILE=$(echo "$LANG_FILE" | xargs)
|
||||
if [ -z "$LANG_FILE" ]; then
|
||||
continue
|
||||
fi
|
||||
# Check in common locations
|
||||
FOUND=0
|
||||
for BASE in "." "src" "htdocs"; do
|
||||
if [ -f "${BASE}/${LANG_FILE}" ]; then
|
||||
FOUND=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "$FOUND" -eq 0 ]; then
|
||||
echo "Missing language file: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "Language file present: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
done <<< "$LANG_FILES"
|
||||
fi
|
||||
else
|
||||
echo "No manifest found — skipping language check." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${ERRORS}" -gt 0 ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**${ERRORS} missing language file(s).**" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Check index.html files in directories
|
||||
run: |
|
||||
echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY
|
||||
MISSING=0
|
||||
CHECKED=0
|
||||
|
||||
for DIR in src/ htdocs/; do
|
||||
if [ -d "$DIR" ]; then
|
||||
while IFS= read -r -d '' SUBDIR; do
|
||||
CHECKED=$((CHECKED + 1))
|
||||
if [ ! -f "${SUBDIR}/index.html" ]; then
|
||||
echo "Missing index.html in: \`${SUBDIR}\`" >> $GITHUB_STEP_SUMMARY
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
done < <(find "$DIR" -type d -print0)
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${CHECKED}" -eq 0 ]; then
|
||||
echo "No src/ or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${MISSING}" -gt 0 ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
else
|
||||
echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
release-readiness:
|
||||
name: Release Readiness Check
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request' && github.base_ref == 'main'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Validate release readiness
|
||||
run: |
|
||||
echo "## Release Readiness" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=0
|
||||
|
||||
# Extract version from README.md
|
||||
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
|
||||
if [ -z "$README_VERSION" ]; then
|
||||
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "README version: \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# Find the extension manifest
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No Joomla extension manifest found." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Check <version> matches README VERSION
|
||||
MANIFEST_VERSION=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
|
||||
if [ -z "$MANIFEST_VERSION" ]; then
|
||||
echo "No \`<version>\` tag in manifest." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
elif [ -n "$README_VERSION" ] && [ "$MANIFEST_VERSION" != "$README_VERSION" ]; then
|
||||
echo "Manifest version \`${MANIFEST_VERSION}\` does not match README \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "Manifest version: \`${MANIFEST_VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# Check extension type, element, client attributes
|
||||
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
|
||||
if [ -z "$EXT_TYPE" ]; then
|
||||
echo "Missing \`type\` attribute on \`<extension>\` tag." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "Extension type: \`${EXT_TYPE}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# Element check (component/module/plugin name)
|
||||
HAS_ELEMENT=$(grep -cP '<(element|name)>' "$MANIFEST" 2>/dev/null || echo "0")
|
||||
if [ "$HAS_ELEMENT" -eq 0 ]; then
|
||||
echo "Missing \`<element>\` or \`<name>\` in manifest." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
# Client attribute for site/admin modules and plugins
|
||||
if echo "$EXT_TYPE" | grep -qP "^(module|plugin)$"; then
|
||||
HAS_CLIENT=$(grep -cP '<extension[^>]*\bclient=' "$MANIFEST" 2>/dev/null || echo "0")
|
||||
if [ "$HAS_CLIENT" -eq 0 ]; then
|
||||
echo "Missing \`client\` attribute for ${EXT_TYPE} extension." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check updates.xml exists
|
||||
if [ -f "updates.xml" ] || [ -f "updates.xml" ]; then
|
||||
echo "Update XML present." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "No updates.xml found." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
# Check CHANGELOG.md exists
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
echo "CHANGELOG.md present." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "No CHANGELOG.md found." >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ $ERRORS -gt 0 ]; then
|
||||
echo "**${ERRORS} issue(s) must be resolved before release.**" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
else
|
||||
echo "**Extension is ready for release.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
test:
|
||||
name: Tests (PHP ${{ matrix.php }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint-and-validate
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: ['8.2', '8.3']
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup PHP ${{ matrix.php }}
|
||||
run: |
|
||||
php -v && composer --version
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
if [ -f "composer.json" ]; then
|
||||
composer install \
|
||||
--no-interaction \
|
||||
--prefer-dist \
|
||||
--optimize-autoloader
|
||||
else
|
||||
echo "No composer.json found — skipping dependency install"
|
||||
fi
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
echo "### Test Results (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
|
||||
vendor/bin/phpunit --testdox 2>&1 | tee /tmp/test-output.log
|
||||
EXIT=${PIPESTATUS[0]}
|
||||
if [ $EXIT -eq 0 ]; then
|
||||
echo "All tests passed." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Test failures detected — see log." >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
cat /tmp/test-output.log >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
exit $EXIT
|
||||
else
|
||||
echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
108
.github/workflows/codeql-analysis.yml
vendored
Normal file
108
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# This file is part of a Moko Consulting project.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow.Template
|
||||
# INGROUP: MokoStandards.Security
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/generic/codeql-analysis.yml.template
|
||||
# VERSION: 03.09.03
|
||||
# BRIEF: CodeQL security scanning workflow (generic — all repo types)
|
||||
# NOTE: Deployed to .github/workflows/codeql-analysis.yml in governed repos.
|
||||
# CodeQL does not support PHP directly; JavaScript scans JSON/YAML/shell.
|
||||
# For PHP-specific security scanning see standards-compliance.yml.
|
||||
|
||||
name: CodeQL Security Scanning
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- version/*
|
||||
schedule:
|
||||
# Weekly on Monday at 06:00 UTC
|
||||
- cron: '0 6 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 360
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# CodeQL does not support PHP. Use 'javascript' to scan JSON, YAML,
|
||||
# and shell scripts. Add 'actions' to scan GitHub Actions workflows.
|
||||
language: ['javascript', 'actions']
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: security-extended,security-and-quality
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
upload: true
|
||||
output: sarif-results
|
||||
wait-for-processing: true
|
||||
|
||||
- name: Upload SARIF results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.5.0
|
||||
with:
|
||||
name: codeql-results-${{ matrix.language }}
|
||||
path: sarif-results
|
||||
retention-days: 30
|
||||
|
||||
- name: Step summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "### 🔍 CodeQL — ${{ matrix.language }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
URL="https://github.com/${{ github.repository }}/security/code-scanning"
|
||||
echo "See the [Security tab]($URL) for findings." >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Severity | SLA |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|----------|-----|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Critical | 7 days |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| High | 14 days |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Medium | 30 days |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Low | 60 days / next release |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
summary:
|
||||
name: Security Scan Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: analyze
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "### 🛡️ CodeQL Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
SECURITY_URL="https://github.com/${{ github.repository }}/security"
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "📊 [View all security alerts]($SECURITY_URL)" >> $GITHUB_STEP_SUMMARY
|
||||
128
.github/workflows/deploy-manual.yml
vendored
Normal file
128
.github/workflows/deploy-manual.yml
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Deploy
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
|
||||
# NOTE: Joomla repos use update.xml for distribution. This is for manual
|
||||
# dev server testing only — triggered via workflow_dispatch.
|
||||
|
||||
name: Deploy to Dev (Manual)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
clear_remote:
|
||||
description: 'Delete all remote files before uploading'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: SFTP Deploy to Dev
|
||||
runs-on: release
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup PHP
|
||||
run: |
|
||||
php -v && composer --version
|
||||
|
||||
- name: Setup MokoStandards tools
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
git clone --depth 1 --branch version/04 --quiet \
|
||||
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards.git" \
|
||||
/tmp/mokostandards 2>/dev/null || true
|
||||
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
|
||||
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Check FTP configuration
|
||||
id: check
|
||||
env:
|
||||
HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
|
||||
SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||
PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
run: |
|
||||
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
|
||||
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured — cannot deploy"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
echo "host=$HOST" >> "$GITHUB_OUTPUT"
|
||||
|
||||
REMOTE="${PATH_VAR%/}"
|
||||
[ -n "$SUFFIX" ] && REMOTE="${REMOTE}/${SUFFIX#/}"
|
||||
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
[ -z "$PORT" ] && PORT="22"
|
||||
echo "port=$PORT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Deploy via SFTP
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
env:
|
||||
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||
run: |
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — nothing to deploy"; exit 0; }
|
||||
|
||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
|
||||
> /tmp/sftp-config.json
|
||||
|
||||
if [ -n "$SFTP_KEY" ]; then
|
||||
echo "$SFTP_KEY" > /tmp/deploy_key
|
||||
chmod 600 /tmp/deploy_key
|
||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||
else
|
||||
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
|
||||
fi
|
||||
|
||||
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
|
||||
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
|
||||
|
||||
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null || true)
|
||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards/api/deploy/deploy-joomla.php" ]; then
|
||||
php /tmp/mokostandards/api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
|
||||
else
|
||||
php /tmp/mokostandards/api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
|
||||
fi
|
||||
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
|
||||
echo "### Deploy Skipped — FTP not configured" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
758
.github/workflows/enterprise-firewall-setup.yml
vendored
Normal file
758
.github/workflows/enterprise-firewall-setup.yml
vendored
Normal file
@@ -0,0 +1,758 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# This file is part of a Moko Consulting project.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Firewall
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Enterprise firewall configuration — generates outbound allow-rules including SFTP deployment server
|
||||
# NOTE: Reads DEV_FTP_HOST / DEV_FTP_PORT variables to include SFTP egress rules alongside HTTPS rules.
|
||||
|
||||
name: Enterprise Firewall Configuration
|
||||
|
||||
# This workflow provides firewall configuration guidance for enterprise-ready sites
|
||||
# It generates firewall rules for allowing outbound access to trusted domains
|
||||
# including license providers, documentation sources, package registries,
|
||||
# and the SFTP deployment server (DEV_FTP_HOST / DEV_FTP_PORT).
|
||||
#
|
||||
# Runs automatically when:
|
||||
# - Coding agent workflows are triggered (pull requests with copilot/ prefix)
|
||||
# - Manual workflow dispatch for custom configurations
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
firewall_type:
|
||||
description: 'Target firewall type'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- 'iptables'
|
||||
- 'ufw'
|
||||
- 'firewalld'
|
||||
- 'aws-security-group'
|
||||
- 'azure-nsg'
|
||||
- 'gcp-firewall'
|
||||
- 'cloudflare'
|
||||
- 'all'
|
||||
default: 'all'
|
||||
output_format:
|
||||
description: 'Output format'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- 'shell-script'
|
||||
- 'json'
|
||||
- 'yaml'
|
||||
- 'markdown'
|
||||
- 'all'
|
||||
default: 'markdown'
|
||||
|
||||
# Auto-run when coding agent creates or updates PRs
|
||||
pull_request:
|
||||
branches:
|
||||
- 'copilot/**'
|
||||
- 'agent/**'
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
# Auto-run on push to coding agent branches
|
||||
push:
|
||||
branches:
|
||||
- 'copilot/**'
|
||||
- 'agent/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
generate-firewall-rules:
|
||||
name: Generate Firewall Rules
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Apply Firewall Rules to Runner (Auto-run only)
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
env:
|
||||
DEV_FTP_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
DEV_FTP_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
run: |
|
||||
echo "🔥 Applying firewall rules for coding agent environment..."
|
||||
echo ""
|
||||
echo "This step ensures the GitHub Actions runner can access trusted domains"
|
||||
echo "including license providers, package registries, and documentation sources."
|
||||
echo ""
|
||||
|
||||
# Note: GitHub Actions runners are ephemeral and run in controlled environments
|
||||
# This step documents what domains are being accessed during the workflow
|
||||
# Actual firewall configuration is managed by GitHub
|
||||
|
||||
cat > /tmp/trusted-domains.txt << 'EOF'
|
||||
# Trusted domains for coding agent environment
|
||||
# License Providers
|
||||
www.gnu.org
|
||||
opensource.org
|
||||
choosealicense.com
|
||||
spdx.org
|
||||
creativecommons.org
|
||||
apache.org
|
||||
fsf.org
|
||||
|
||||
# Documentation & Standards
|
||||
semver.org
|
||||
keepachangelog.com
|
||||
conventionalcommits.org
|
||||
|
||||
# GitHub & Related
|
||||
github.com
|
||||
api.github.com
|
||||
docs.github.com
|
||||
raw.githubusercontent.com
|
||||
ghcr.io
|
||||
|
||||
# Package Registries
|
||||
npmjs.com
|
||||
registry.npmjs.org
|
||||
pypi.org
|
||||
files.pythonhosted.org
|
||||
packagist.org
|
||||
repo.packagist.org
|
||||
rubygems.org
|
||||
|
||||
# Platform-Specific
|
||||
joomla.org
|
||||
downloads.joomla.org
|
||||
docs.joomla.org
|
||||
php.net
|
||||
getcomposer.org
|
||||
dolibarr.org
|
||||
wiki.dolibarr.org
|
||||
docs.dolibarr.org
|
||||
|
||||
# Moko Consulting
|
||||
mokoconsulting.tech
|
||||
|
||||
# SFTP Deployment Server (DEV_FTP_HOST)
|
||||
${DEV_FTP_HOST:-<not configured>}
|
||||
|
||||
# Google Services
|
||||
drive.google.com
|
||||
docs.google.com
|
||||
sheets.google.com
|
||||
accounts.google.com
|
||||
storage.googleapis.com
|
||||
fonts.googleapis.com
|
||||
fonts.gstatic.com
|
||||
|
||||
# GitHub Extended
|
||||
upload.github.com
|
||||
objects.githubusercontent.com
|
||||
user-images.githubusercontent.com
|
||||
codeload.github.com
|
||||
pkg.github.com
|
||||
|
||||
# Developer Reference
|
||||
developer.mozilla.org
|
||||
stackoverflow.com
|
||||
git-scm.com
|
||||
|
||||
# CDN & Infrastructure
|
||||
cdn.jsdelivr.net
|
||||
unpkg.com
|
||||
cdnjs.cloudflare.com
|
||||
img.shields.io
|
||||
|
||||
# Container Registries
|
||||
hub.docker.com
|
||||
registry-1.docker.io
|
||||
|
||||
# CI & Code Quality
|
||||
codecov.io
|
||||
sonarcloud.io
|
||||
|
||||
# Terraform & Infrastructure
|
||||
registry.terraform.io
|
||||
releases.hashicorp.com
|
||||
checkpoint-api.hashicorp.com
|
||||
EOF
|
||||
|
||||
echo "✓ Trusted domains documented for this runner"
|
||||
echo "✓ GitHub Actions runners have network access to these domains"
|
||||
echo ""
|
||||
|
||||
# Test connectivity to key domains
|
||||
echo "Testing connectivity to key domains..."
|
||||
for domain in "github.com" "www.gnu.org" "npmjs.com" "pypi.org"; do
|
||||
if curl -s --max-time 3 -o /dev/null -w "%{http_code}" "https://$domain" | grep -q "200\|301\|302"; then
|
||||
echo " ✓ $domain is accessible"
|
||||
else
|
||||
echo " ⚠️ $domain connectivity check failed (may be expected)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Test SFTP server connectivity (TCP port check)
|
||||
SFTP_HOST="${DEV_FTP_HOST:-}"
|
||||
SFTP_PORT="${DEV_FTP_PORT:-22}"
|
||||
if [ -n "$SFTP_HOST" ]; then
|
||||
# Strip any embedded :port suffix
|
||||
SFTP_HOST="${SFTP_HOST%%:*}"
|
||||
echo ""
|
||||
echo "Testing SFTP deployment server connectivity..."
|
||||
if timeout 5 bash -c "echo >/dev/tcp/${SFTP_HOST}/${SFTP_PORT}" 2>/dev/null; then
|
||||
echo " ✓ SFTP server ${SFTP_HOST}:${SFTP_PORT} is reachable"
|
||||
else
|
||||
echo " ⚠️ SFTP server ${SFTP_HOST}:${SFTP_PORT} is not reachable from runner (firewall rule needed)"
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo " ℹ️ DEV_FTP_HOST not configured — skipping SFTP connectivity check"
|
||||
fi
|
||||
|
||||
- name: Generate Firewall Configuration
|
||||
id: generate
|
||||
env:
|
||||
DEV_FTP_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
DEV_FTP_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
run: |
|
||||
cat > generate_firewall_config.py << 'PYTHON_EOF'
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enterprise Firewall Configuration Generator
|
||||
|
||||
Generates firewall rules for enterprise-ready deployments allowing
|
||||
access to trusted domains including license providers, documentation
|
||||
sources, package registries, and platform-specific sites.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import yaml
|
||||
import sys
|
||||
from typing import List, Dict
|
||||
|
||||
# SFTP deployment server from org variables
|
||||
_sftp_host_raw = os.environ.get("DEV_FTP_HOST", "").strip()
|
||||
_sftp_port = os.environ.get("DEV_FTP_PORT", "").strip() or "22"
|
||||
# Strip embedded :port suffix if present
|
||||
_sftp_host = _sftp_host_raw.split(":")[0] if _sftp_host_raw else ""
|
||||
if ":" in _sftp_host_raw and not _sftp_port:
|
||||
_sftp_port = _sftp_host_raw.split(":")[1]
|
||||
|
||||
SFTP_HOST = _sftp_host
|
||||
SFTP_PORT = int(_sftp_port) if _sftp_port.isdigit() else 22
|
||||
|
||||
# Trusted domains from .github/copilot.yml
|
||||
TRUSTED_DOMAINS = {
|
||||
"license_providers": [
|
||||
"www.gnu.org",
|
||||
"opensource.org",
|
||||
"choosealicense.com",
|
||||
"spdx.org",
|
||||
"creativecommons.org",
|
||||
"apache.org",
|
||||
"fsf.org",
|
||||
],
|
||||
"documentation_standards": [
|
||||
"semver.org",
|
||||
"keepachangelog.com",
|
||||
"conventionalcommits.org",
|
||||
],
|
||||
"github_related": [
|
||||
"github.com",
|
||||
"api.github.com",
|
||||
"docs.github.com",
|
||||
"raw.githubusercontent.com",
|
||||
"ghcr.io",
|
||||
],
|
||||
"package_registries": [
|
||||
"npmjs.com",
|
||||
"registry.npmjs.org",
|
||||
"pypi.org",
|
||||
"files.pythonhosted.org",
|
||||
"packagist.org",
|
||||
"repo.packagist.org",
|
||||
"rubygems.org",
|
||||
],
|
||||
"standards_organizations": [
|
||||
"json-schema.org",
|
||||
"w3.org",
|
||||
"ietf.org",
|
||||
],
|
||||
"platform_specific": [
|
||||
"joomla.org",
|
||||
"downloads.joomla.org",
|
||||
"docs.joomla.org",
|
||||
"php.net",
|
||||
"getcomposer.org",
|
||||
"dolibarr.org",
|
||||
"wiki.dolibarr.org",
|
||||
"docs.dolibarr.org",
|
||||
],
|
||||
"moko_consulting": [
|
||||
"mokoconsulting.tech",
|
||||
],
|
||||
"google_services": [
|
||||
"drive.google.com",
|
||||
"docs.google.com",
|
||||
"sheets.google.com",
|
||||
"accounts.google.com",
|
||||
"storage.googleapis.com",
|
||||
"fonts.googleapis.com",
|
||||
"fonts.gstatic.com",
|
||||
],
|
||||
"github_extended": [
|
||||
"upload.github.com",
|
||||
"objects.githubusercontent.com",
|
||||
"user-images.githubusercontent.com",
|
||||
"codeload.github.com",
|
||||
"pkg.github.com",
|
||||
],
|
||||
"developer_reference": [
|
||||
"developer.mozilla.org",
|
||||
"stackoverflow.com",
|
||||
"git-scm.com",
|
||||
],
|
||||
"cdn_and_infrastructure": [
|
||||
"cdn.jsdelivr.net",
|
||||
"unpkg.com",
|
||||
"cdnjs.cloudflare.com",
|
||||
"img.shields.io",
|
||||
],
|
||||
"container_registries": [
|
||||
"hub.docker.com",
|
||||
"registry-1.docker.io",
|
||||
],
|
||||
"ci_code_quality": [
|
||||
"codecov.io",
|
||||
"sonarcloud.io",
|
||||
],
|
||||
"terraform_infrastructure": [
|
||||
"registry.terraform.io",
|
||||
"releases.hashicorp.com",
|
||||
"checkpoint-api.hashicorp.com",
|
||||
],
|
||||
}
|
||||
|
||||
# Inject SFTP deployment server as a separate category (port 22, not 443)
|
||||
if SFTP_HOST:
|
||||
TRUSTED_DOMAINS["sftp_deployment_server"] = [SFTP_HOST]
|
||||
print(f"ℹ️ SFTP deployment server: {SFTP_HOST}:{SFTP_PORT}")
|
||||
|
||||
def generate_sftp_iptables_rules(host: str, port: int) -> str:
|
||||
"""Generate iptables rules specifically for SFTP egress"""
|
||||
return (
|
||||
f"# Allow SFTP to deployment server {host}:{port}\n"
|
||||
f"iptables -A OUTPUT -p tcp -d $(dig +short {host} | head -1)"
|
||||
f" --dport {port} -j ACCEPT # SFTP deploy\n"
|
||||
)
|
||||
|
||||
def generate_sftp_ufw_rules(host: str, port: int) -> str:
|
||||
"""Generate UFW rules for SFTP egress"""
|
||||
return (
|
||||
f"# Allow SFTP to deployment server\n"
|
||||
f"ufw allow out to $(dig +short {host} | head -1)"
|
||||
f" port {port} proto tcp comment 'SFTP deploy to {host}'\n"
|
||||
)
|
||||
|
||||
def generate_sftp_firewalld_rules(host: str, port: int) -> str:
|
||||
"""Generate firewalld rules for SFTP egress"""
|
||||
return (
|
||||
f"# Allow SFTP to deployment server\n"
|
||||
f"firewall-cmd --permanent --add-rich-rule='"
|
||||
f"rule family=ipv4 destination address=$(dig +short {host} | head -1)"
|
||||
f" port port={port} protocol=tcp accept' # SFTP deploy\n"
|
||||
)
|
||||
|
||||
def generate_iptables_rules(domains: List[str]) -> str:
|
||||
"""Generate iptables firewall rules"""
|
||||
rules = ["#!/bin/bash", "", "# Enterprise Firewall Rules - iptables", ""]
|
||||
rules.append("# Allow outbound HTTPS to trusted domains")
|
||||
rules.append("")
|
||||
|
||||
for domain in domains:
|
||||
rules.append(f"# Allow {domain}")
|
||||
rules.append(f"iptables -A OUTPUT -p tcp -d $(dig +short {domain} | head -1) --dport 443 -j ACCEPT")
|
||||
|
||||
rules.append("")
|
||||
rules.append("# Allow DNS lookups")
|
||||
rules.append("iptables -A OUTPUT -p udp --dport 53 -j ACCEPT")
|
||||
rules.append("iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT")
|
||||
|
||||
return "\n".join(rules)
|
||||
|
||||
def generate_ufw_rules(domains: List[str]) -> str:
|
||||
"""Generate UFW firewall rules"""
|
||||
rules = ["#!/bin/bash", "", "# Enterprise Firewall Rules - UFW", ""]
|
||||
rules.append("# Allow outbound HTTPS to trusted domains")
|
||||
rules.append("")
|
||||
|
||||
for domain in domains:
|
||||
rules.append(f"# Allow {domain}")
|
||||
rules.append(f"ufw allow out to $(dig +short {domain} | head -1) port 443 proto tcp comment 'Allow {domain}'")
|
||||
|
||||
rules.append("")
|
||||
rules.append("# Allow DNS")
|
||||
rules.append("ufw allow out 53/udp comment 'Allow DNS UDP'")
|
||||
rules.append("ufw allow out 53/tcp comment 'Allow DNS TCP'")
|
||||
|
||||
return "\n".join(rules)
|
||||
|
||||
def generate_firewalld_rules(domains: List[str]) -> str:
|
||||
"""Generate firewalld rules"""
|
||||
rules = ["#!/bin/bash", "", "# Enterprise Firewall Rules - firewalld", ""]
|
||||
rules.append("# Add trusted domains to firewall")
|
||||
rules.append("")
|
||||
|
||||
for domain in domains:
|
||||
rules.append(f"# Allow {domain}")
|
||||
rules.append(f"firewall-cmd --permanent --add-rich-rule='rule family=ipv4 destination address=$(dig +short {domain} | head -1) port port=443 protocol=tcp accept'")
|
||||
|
||||
rules.append("")
|
||||
rules.append("# Reload firewall")
|
||||
rules.append("firewall-cmd --reload")
|
||||
|
||||
return "\n".join(rules)
|
||||
|
||||
def generate_aws_security_group(domains: List[str]) -> Dict:
|
||||
"""Generate AWS Security Group rules (JSON format)"""
|
||||
rules = {
|
||||
"SecurityGroupRules": {
|
||||
"Egress": []
|
||||
}
|
||||
}
|
||||
|
||||
for domain in domains:
|
||||
rules["SecurityGroupRules"]["Egress"].append({
|
||||
"Description": f"Allow HTTPS to {domain}",
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 443,
|
||||
"ToPort": 443,
|
||||
"CidrIp": "0.0.0.0/0", # In practice, resolve to specific IPs
|
||||
"Tags": [{
|
||||
"Key": "Domain",
|
||||
"Value": domain
|
||||
}]
|
||||
})
|
||||
|
||||
# Add DNS
|
||||
rules["SecurityGroupRules"]["Egress"].append({
|
||||
"Description": "Allow DNS",
|
||||
"IpProtocol": "udp",
|
||||
"FromPort": 53,
|
||||
"ToPort": 53,
|
||||
"CidrIp": "0.0.0.0/0"
|
||||
})
|
||||
|
||||
return rules
|
||||
|
||||
def generate_markdown_documentation(domains_by_category: Dict[str, List[str]]) -> str:
|
||||
"""Generate markdown documentation"""
|
||||
md = ["# Enterprise Firewall Configuration Guide", ""]
|
||||
md.append("## Overview")
|
||||
md.append("")
|
||||
md.append("This document provides firewall configuration guidance for enterprise-ready deployments.")
|
||||
md.append("It lists trusted domains that should be whitelisted for outbound access to ensure")
|
||||
md.append("proper functionality of license validation, package management, and documentation access.")
|
||||
md.append("")
|
||||
|
||||
md.append("## Trusted Domains by Category")
|
||||
md.append("")
|
||||
|
||||
all_domains = []
|
||||
for category, domains in domains_by_category.items():
|
||||
category_name = category.replace("_", " ").title()
|
||||
md.append(f"### {category_name}")
|
||||
md.append("")
|
||||
md.append("| Domain | Purpose |")
|
||||
md.append("|--------|---------|")
|
||||
|
||||
for domain in domains:
|
||||
all_domains.append(domain)
|
||||
purpose = get_domain_purpose(domain)
|
||||
md.append(f"| `{domain}` | {purpose} |")
|
||||
|
||||
md.append("")
|
||||
|
||||
md.append("## Implementation Examples")
|
||||
md.append("")
|
||||
|
||||
md.append("### iptables Example")
|
||||
md.append("")
|
||||
md.append("```bash")
|
||||
md.append("# Allow HTTPS to trusted domain")
|
||||
md.append(f"iptables -A OUTPUT -p tcp -d $(dig +short {all_domains[0]}) --dport 443 -j ACCEPT")
|
||||
md.append("```")
|
||||
md.append("")
|
||||
|
||||
md.append("### UFW Example")
|
||||
md.append("")
|
||||
md.append("```bash")
|
||||
md.append("# Allow HTTPS to trusted domain")
|
||||
md.append(f"ufw allow out to {all_domains[0]} port 443 proto tcp")
|
||||
md.append("```")
|
||||
md.append("")
|
||||
|
||||
md.append("### AWS Security Group Example")
|
||||
md.append("")
|
||||
md.append("```json")
|
||||
md.append("{")
|
||||
md.append(' "IpPermissions": [{')
|
||||
md.append(' "IpProtocol": "tcp",')
|
||||
md.append(' "FromPort": 443,')
|
||||
md.append(' "ToPort": 443,')
|
||||
md.append(' "IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTPS to trusted domains"}]')
|
||||
md.append(" }]")
|
||||
md.append("}")
|
||||
md.append("```")
|
||||
md.append("")
|
||||
|
||||
md.append("## Ports Required")
|
||||
md.append("")
|
||||
md.append("| Port | Protocol | Purpose |")
|
||||
md.append("|------|----------|---------|")
|
||||
md.append("| 443 | TCP | HTTPS (secure web access) |")
|
||||
md.append("| 80 | TCP | HTTP (redirects to HTTPS) |")
|
||||
md.append("| 53 | UDP/TCP | DNS resolution |")
|
||||
md.append("")
|
||||
|
||||
md.append("## Security Considerations")
|
||||
md.append("")
|
||||
md.append("1. **DNS Resolution**: Ensure DNS queries are allowed (port 53 UDP/TCP)")
|
||||
md.append("2. **Certificate Validation**: HTTPS requires ability to reach certificate authorities")
|
||||
md.append("3. **Dynamic IPs**: Some domains use CDNs with dynamic IPs - consider using FQDNs in rules")
|
||||
md.append("4. **Regular Updates**: Review and update whitelist as services change")
|
||||
md.append("5. **Logging**: Enable logging for blocked connections to identify missing rules")
|
||||
md.append("")
|
||||
|
||||
md.append("## Compliance Notes")
|
||||
md.append("")
|
||||
md.append("- All listed domains provide read-only access to public information")
|
||||
md.append("- License providers enable GPL compliance verification")
|
||||
md.append("- Package registries support dependency security scanning")
|
||||
md.append("- No authentication credentials are transmitted to these domains")
|
||||
md.append("")
|
||||
|
||||
return "\n".join(md)
|
||||
|
||||
def get_domain_purpose(domain: str) -> str:
|
||||
"""Get human-readable purpose for a domain"""
|
||||
purposes = {
|
||||
"www.gnu.org": "GNU licenses and documentation",
|
||||
"opensource.org": "Open Source Initiative resources",
|
||||
"choosealicense.com": "GitHub license selection tool",
|
||||
"spdx.org": "Software Package Data Exchange identifiers",
|
||||
"creativecommons.org": "Creative Commons licenses",
|
||||
"apache.org": "Apache Software Foundation licenses",
|
||||
"fsf.org": "Free Software Foundation resources",
|
||||
"semver.org": "Semantic versioning specification",
|
||||
"keepachangelog.com": "Changelog format standards",
|
||||
"conventionalcommits.org": "Commit message conventions",
|
||||
"github.com": "GitHub platform access",
|
||||
"api.github.com": "GitHub API access",
|
||||
"docs.github.com": "GitHub documentation",
|
||||
"raw.githubusercontent.com": "GitHub raw content access",
|
||||
"npmjs.com": "npm package registry",
|
||||
"pypi.org": "Python Package Index",
|
||||
"packagist.org": "PHP Composer package registry",
|
||||
"rubygems.org": "Ruby gems registry",
|
||||
"joomla.org": "Joomla CMS platform",
|
||||
"php.net": "PHP documentation and downloads",
|
||||
"dolibarr.org": "Dolibarr ERP/CRM platform",
|
||||
}
|
||||
return purposes.get(domain, "Trusted resource")
|
||||
|
||||
def main():
|
||||
# Use inputs if provided (manual dispatch), otherwise use defaults (auto-run)
|
||||
firewall_type = "${{ github.event.inputs.firewall_type }}" or "all"
|
||||
output_format = "${{ github.event.inputs.output_format }}" or "markdown"
|
||||
|
||||
print(f"Running in {'manual' if '${{ github.event.inputs.firewall_type }}' else 'automatic'} mode")
|
||||
print(f"Firewall type: {firewall_type}")
|
||||
print(f"Output format: {output_format}")
|
||||
print("")
|
||||
|
||||
# Collect all domains
|
||||
all_domains = []
|
||||
for domains in TRUSTED_DOMAINS.values():
|
||||
all_domains.extend(domains)
|
||||
|
||||
# Remove duplicates and sort
|
||||
all_domains = sorted(set(all_domains))
|
||||
|
||||
print(f"Generating firewall rules for {len(all_domains)} trusted domains...")
|
||||
print("")
|
||||
|
||||
# Exclude SFTP server from HTTPS rule generation (different port)
|
||||
https_domains = [d for d in all_domains if d != SFTP_HOST]
|
||||
|
||||
# Generate based on firewall type
|
||||
if firewall_type in ["iptables", "all"]:
|
||||
rules = generate_iptables_rules(https_domains)
|
||||
if SFTP_HOST:
|
||||
rules += "\n# ── SFTP Deployment Server ──────────────────────────────\n"
|
||||
rules += generate_sftp_iptables_rules(SFTP_HOST, SFTP_PORT)
|
||||
with open("firewall-rules-iptables.sh", "w") as f:
|
||||
f.write(rules)
|
||||
print("✓ Generated iptables rules: firewall-rules-iptables.sh")
|
||||
|
||||
if firewall_type in ["ufw", "all"]:
|
||||
rules = generate_ufw_rules(https_domains)
|
||||
if SFTP_HOST:
|
||||
rules += "\n# ── SFTP Deployment Server ──────────────────────────────\n"
|
||||
rules += generate_sftp_ufw_rules(SFTP_HOST, SFTP_PORT)
|
||||
with open("firewall-rules-ufw.sh", "w") as f:
|
||||
f.write(rules)
|
||||
print("✓ Generated UFW rules: firewall-rules-ufw.sh")
|
||||
|
||||
if firewall_type in ["firewalld", "all"]:
|
||||
rules = generate_firewalld_rules(https_domains)
|
||||
if SFTP_HOST:
|
||||
rules += "\n# ── SFTP Deployment Server ──────────────────────────────\n"
|
||||
rules += generate_sftp_firewalld_rules(SFTP_HOST, SFTP_PORT)
|
||||
with open("firewall-rules-firewalld.sh", "w") as f:
|
||||
f.write(rules)
|
||||
print("✓ Generated firewalld rules: firewall-rules-firewalld.sh")
|
||||
|
||||
if firewall_type in ["aws-security-group", "all"]:
|
||||
rules = generate_aws_security_group(all_domains)
|
||||
with open("firewall-rules-aws-sg.json", "w") as f:
|
||||
json.dump(rules, f, indent=2)
|
||||
print("✓ Generated AWS Security Group rules: firewall-rules-aws-sg.json")
|
||||
|
||||
if output_format in ["yaml", "all"]:
|
||||
with open("trusted-domains.yml", "w") as f:
|
||||
yaml.dump(TRUSTED_DOMAINS, f, default_flow_style=False)
|
||||
print("✓ Generated YAML domain list: trusted-domains.yml")
|
||||
|
||||
if output_format in ["json", "all"]:
|
||||
with open("trusted-domains.json", "w") as f:
|
||||
json.dump(TRUSTED_DOMAINS, f, indent=2)
|
||||
print("✓ Generated JSON domain list: trusted-domains.json")
|
||||
|
||||
if output_format in ["markdown", "all"]:
|
||||
md = generate_markdown_documentation(TRUSTED_DOMAINS)
|
||||
with open("FIREWALL_CONFIGURATION.md", "w") as f:
|
||||
f.write(md)
|
||||
print("✓ Generated documentation: FIREWALL_CONFIGURATION.md")
|
||||
|
||||
print("")
|
||||
print("Domain Categories:")
|
||||
for category, domains in TRUSTED_DOMAINS.items():
|
||||
print(f" - {category}: {len(domains)} domains")
|
||||
|
||||
print("")
|
||||
print("Total unique domains: ", len(all_domains))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
PYTHON_EOF
|
||||
|
||||
chmod +x generate_firewall_config.py
|
||||
pip install PyYAML
|
||||
python3 generate_firewall_config.py
|
||||
|
||||
- name: Upload Firewall Configuration Artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: firewall-configurations
|
||||
path: |
|
||||
firewall-rules-*.sh
|
||||
firewall-rules-*.json
|
||||
trusted-domains.*
|
||||
FIREWALL_CONFIGURATION.md
|
||||
retention-days: 90
|
||||
|
||||
- name: Display Summary
|
||||
run: |
|
||||
echo "## Firewall Configuration" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "**Mode**: Manual Execution" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Firewall rules have been generated for enterprise-ready deployments." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "**Mode**: Automatic Execution (Coding Agent Active)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "This workflow ran automatically because a coding agent (GitHub Copilot) is active." >> $GITHUB_STEP_SUMMARY
|
||||
echo "Firewall configuration has been validated for the coding agent environment." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Files Generated" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if ls firewall-rules-* trusted-domains.* FIREWALL_CONFIGURATION.md 2>/dev/null; then
|
||||
ls -lh firewall-rules-* trusted-domains.* FIREWALL_CONFIGURATION.md 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "- Documentation generated" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "### Download Artifacts" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Download the generated firewall configurations from the workflow artifacts." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "### Trusted Domains Active" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The coding agent has access to:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- License providers (GPL, OSI, SPDX, Apache, etc.)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Package registries (npm, PyPI, Packagist, RubyGems)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Documentation sources (GitHub, Joomla, Dolibarr, PHP)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Standards organizations (W3C, IETF, JSON Schema)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# Usage Instructions:
|
||||
#
|
||||
# This workflow runs in two modes:
|
||||
#
|
||||
# 1. AUTOMATIC MODE (Coding Agent):
|
||||
# - Triggers when coding agent branches (copilot/**, agent/**) are pushed or PR'd
|
||||
# - Validates firewall configuration for the coding agent environment
|
||||
# - Documents accessible domains for compliance
|
||||
# - Ensures license sources and package registries are available
|
||||
#
|
||||
# 2. MANUAL MODE (Enterprise Configuration):
|
||||
# - Manually trigger from the Actions tab
|
||||
# - Select desired firewall type and output format
|
||||
# - Download generated artifacts
|
||||
# - Apply firewall rules to your enterprise environment
|
||||
#
|
||||
# Configuration:
|
||||
# - Trusted domains are sourced from .github/copilot.yml
|
||||
# - Modify copilot.yml to add/remove trusted domains
|
||||
# - Changes automatically propagate to firewall rules
|
||||
#
|
||||
# Important Notes:
|
||||
# - Review generated rules before applying to production
|
||||
# - Some domains may use CDNs with dynamic IPs
|
||||
# - Consider using FQDN-based rules where supported
|
||||
# - Test thoroughly in staging environment first
|
||||
# - Monitor logs for blocked connections
|
||||
# - Update rules as domains/services change
|
||||
446
.github/workflows/release.yml
vendored
Normal file
446
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,446 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# This file is part of a Moko Consulting project.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoOnyx.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
# PATH: /.github/workflows/release.yml
|
||||
# VERSION: 03.09.16
|
||||
# BRIEF: Joomla release — build ZIP, publish to Gitea, mirror to GitHub
|
||||
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9][0-9].[0-9][0-9].[0-9][0-9]'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version (e.g., 03.09.16)'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Mark as pre-release'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
stability:
|
||||
description: 'Stability tag (development, alpha, beta, rc, stable)'
|
||||
required: false
|
||||
type: string
|
||||
default: 'development'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITEA_URL: https://git.mokoconsulting.tech
|
||||
GITEA_ORG: MokoConsulting
|
||||
GITEA_REPO: MokoOnyx
|
||||
EXT_ELEMENT: mokoonyx
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Release Package
|
||||
runs-on: release
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PHP
|
||||
run: |
|
||||
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
|
||||
php -v
|
||||
composer --version
|
||||
|
||||
- name: Get version and stability
|
||||
id: meta
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
VERSION="${{ inputs.version }}"
|
||||
STABILITY="${{ inputs.stability }}"
|
||||
PRERELEASE="${{ inputs.prerelease }}"
|
||||
else
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
STABILITY="stable"
|
||||
PRERELEASE="false"
|
||||
fi
|
||||
|
||||
# Derive suffix and tag from stability
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG_NAME="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG_NAME="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG_NAME="beta" ;;
|
||||
rc) SUFFIX="-rc"; TAG_NAME="release-candidate" ;;
|
||||
stable) SUFFIX=""; TAG_NAME="v${VERSION%%.*}" ;;
|
||||
*) SUFFIX="-dev"; TAG_NAME="development" ;;
|
||||
esac
|
||||
|
||||
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "prerelease=${PRERELEASE}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag_name=${TAG_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "Building: ${ZIP_NAME} (${STABILITY})"
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}'
|
||||
run: |
|
||||
if [ -f "composer.json" ]; then
|
||||
composer install --no-dev --optimize-autoloader --no-interaction
|
||||
fi
|
||||
|
||||
- name: Create package
|
||||
run: |
|
||||
mkdir -p build/package
|
||||
rsync -av \
|
||||
--exclude='sftp-config*' \
|
||||
--exclude='.ftpignore' \
|
||||
--exclude='*.ppk' \
|
||||
--exclude='*.pem' \
|
||||
--exclude='*.key' \
|
||||
--exclude='.env*' \
|
||||
--exclude='*.local' \
|
||||
src/ build/package/
|
||||
|
||||
- name: Build ZIP
|
||||
id: zip
|
||||
run: |
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
cd build/package
|
||||
zip -r "../${ZIP_NAME}" .
|
||||
cd ..
|
||||
|
||||
SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
|
||||
SIZE=$(stat -c%s "${ZIP_NAME}")
|
||||
|
||||
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
|
||||
echo "size=${SIZE}" >> "$GITHUB_OUTPUT"
|
||||
echo "SHA-256: ${SHA256}"
|
||||
echo "Size: ${SIZE} bytes"
|
||||
|
||||
# ── Gitea Release (PRIMARY) ──────────────────────────────────────
|
||||
- name: "Gitea: Delete existing release"
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Find and delete existing release by tag (may not exist — ignore 404)
|
||||
RELEASE_ID=$(curl -s -H "Authorization: token ${TOKEN}" \
|
||||
"${API}/releases/tags/${TAG}" 2>/dev/null | jq -r '.id // empty')
|
||||
|
||||
if [ -n "$RELEASE_ID" ]; then
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API}/releases/${RELEASE_ID}" || true
|
||||
echo "Deleted existing release id=${RELEASE_ID}"
|
||||
fi
|
||||
|
||||
# Delete existing tag
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API}/tags/${TAG}" 2>/dev/null || true
|
||||
|
||||
- name: "Gitea: Create release"
|
||||
id: gitea_release
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
PRERELEASE="${{ steps.meta.outputs.prerelease }}"
|
||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Build release body
|
||||
BODY="## ${EXT_ELEMENT} ${VERSION} (${STABILITY})
|
||||
|
||||
### SHA-256
|
||||
\`${SHA256}\`"
|
||||
|
||||
# Extract changelog if available
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk "/## \[${VERSION}\]/,/## \[/{if(/## \[${VERSION}\]/)next;if(/## \[/)exit;print}" CHANGELOG.md)
|
||||
if [ -n "$NOTES" ]; then
|
||||
BODY="## ${EXT_ELEMENT} ${VERSION} (${STABILITY})
|
||||
|
||||
${NOTES}
|
||||
|
||||
### SHA-256
|
||||
\`${SHA256}\`"
|
||||
fi
|
||||
fi
|
||||
|
||||
IS_PRE="true"
|
||||
if [ "$STABILITY" = "stable" ]; then
|
||||
IS_PRE="false"
|
||||
fi
|
||||
|
||||
RESULT=$(curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/releases" \
|
||||
-d "$(jq -n \
|
||||
--arg tag "$TAG" \
|
||||
--arg target "${{ github.ref_name }}" \
|
||||
--arg name "${EXT_ELEMENT} ${VERSION} ${STABILITY^}" \
|
||||
--arg body "$BODY" \
|
||||
--argjson pre "$IS_PRE" \
|
||||
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: $pre}'
|
||||
)")
|
||||
|
||||
RELEASE_ID=$(echo "$RESULT" | jq -r '.id')
|
||||
echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
|
||||
echo "Gitea release created: id=${RELEASE_ID}, tag=${TAG}"
|
||||
|
||||
- name: "Gitea: Upload ZIP"
|
||||
run: |
|
||||
RELEASE_ID="${{ steps.gitea_release.outputs.release_id }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
|
||||
--data-binary "@build/${ZIP_NAME}"
|
||||
|
||||
echo "Uploaded ${ZIP_NAME} to Gitea release ${RELEASE_ID}"
|
||||
|
||||
# ── GitHub Mirror (BACKUP) ───────────────────────────────────────
|
||||
- name: "GitHub: Mirror release (stable/rc only)"
|
||||
if: ${{ steps.meta.outputs.stability == 'stable' || steps.meta.outputs.stability == 'rc' }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||
TOKEN="${{ secrets.GH_TOKEN }}"
|
||||
GH_REPO="mokoconsulting-tech/${GITEA_REPO}"
|
||||
GH_API="https://api.github.com/repos/${GH_REPO}"
|
||||
|
||||
IS_PRE="true"
|
||||
[ "$STABILITY" = "stable" ] && IS_PRE="false"
|
||||
|
||||
# Delete existing release by tag
|
||||
EXISTING=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||
"${GH_API}/releases/tags/${TAG}" 2>/dev/null | jq -r '.id // empty')
|
||||
if [ -n "$EXISTING" ]; then
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${GH_API}/releases/${EXISTING}" || true
|
||||
fi
|
||||
|
||||
# Delete tag
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${GH_API}/git/refs/tags/${TAG}" 2>/dev/null || true
|
||||
|
||||
# Create release
|
||||
RELEASE_ID=$(curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${GH_API}/releases" \
|
||||
-d "$(jq -n \
|
||||
--arg tag "$TAG" \
|
||||
--arg target "${{ github.sha }}" \
|
||||
--arg name "${EXT_ELEMENT} ${VERSION} ${STABILITY^} (mirror)" \
|
||||
--arg body "Mirror of Gitea release. SHA-256: \`${SHA256}\`" \
|
||||
--argjson pre "$IS_PRE" \
|
||||
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: $pre, draft: false}'
|
||||
)" | jq -r '.id')
|
||||
|
||||
# Upload ZIP
|
||||
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "null" ]; then
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
"https://uploads.github.com/repos/${GH_REPO}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
|
||||
--data-binary "@build/${ZIP_NAME}"
|
||||
echo "GitHub mirror: uploaded ${ZIP_NAME}"
|
||||
fi
|
||||
|
||||
# ── Update updates.xml ──────────────────────────────────────────
|
||||
- name: "Update updates.xml for this channel"
|
||||
run: |
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
|
||||
if [ ! -f "updates.xml" ] || [ -z "$SHA256" ]; then
|
||||
echo "No updates.xml or no SHA — skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export PY_STABILITY="$STABILITY" PY_VERSION="$VERSION" PY_SHA256="$SHA256" \
|
||||
PY_ZIP_NAME="$ZIP_NAME" PY_TAG="$TAG" PY_DATE="$DATE" \
|
||||
PY_GITEA_ORG="$GITEA_ORG" PY_GITEA_REPO="$GITEA_REPO"
|
||||
python3 << 'PYEOF'
|
||||
import re, os
|
||||
|
||||
stability = os.environ["PY_STABILITY"]
|
||||
version = os.environ["PY_VERSION"]
|
||||
sha256 = os.environ["PY_SHA256"]
|
||||
zip_name = os.environ["PY_ZIP_NAME"]
|
||||
tag = os.environ["PY_TAG"]
|
||||
date = os.environ["PY_DATE"]
|
||||
gitea_org = os.environ["PY_GITEA_ORG"]
|
||||
gitea_repo = os.environ["PY_GITEA_REPO"]
|
||||
|
||||
# Map stability to the <tag> value in updates.xml
|
||||
tag_map = {
|
||||
"development": "development",
|
||||
"alpha": "alpha",
|
||||
"beta": "beta",
|
||||
"rc": "rc",
|
||||
"stable": "stable",
|
||||
}
|
||||
xml_tag = tag_map.get(stability, "development")
|
||||
|
||||
with open("updates.xml", "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# Build regex to find the specific <update> block for this stability tag
|
||||
# Use negative lookahead to avoid matching across multiple <update> blocks
|
||||
block_pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(xml_tag) + r"</tag>.*?</update>)"
|
||||
match = re.search(block_pattern, content, re.DOTALL)
|
||||
|
||||
if not match:
|
||||
print(f"No <update> block found for <tag>{xml_tag}</tag>")
|
||||
exit(0)
|
||||
|
||||
block = match.group(1)
|
||||
original_block = block
|
||||
|
||||
# Update version
|
||||
block = re.sub(r"<version>[^<]*</version>", f"<version>{version}</version>", block)
|
||||
|
||||
# Update creation date
|
||||
block = re.sub(r"<creationDate>[^<]*</creationDate>", f"<creationDate>{date}</creationDate>", block)
|
||||
|
||||
# Update SHA-256
|
||||
block = re.sub(r"<sha256>[^<]*</sha256>", f"<sha256>{sha256}</sha256>", block)
|
||||
|
||||
# Update Gitea download URL
|
||||
gitea_url = f"https://git.mokoconsulting.tech/{gitea_org}/{gitea_repo}/releases/download/{tag}/{zip_name}"
|
||||
block = re.sub(
|
||||
r"(<downloadurl[^>]*>)https://git\.mokoconsulting\.tech/[^<]*(</downloadurl>)",
|
||||
rf"\g<1>{gitea_url}\g<2>",
|
||||
block
|
||||
)
|
||||
|
||||
# Update GitHub download URL only for RC and stable (others are Gitea-only)
|
||||
if stability in ("rc", "stable"):
|
||||
gh_url = f"https://github.com/mokoconsulting-tech/{gitea_repo}/releases/download/{tag}/{zip_name}"
|
||||
block = re.sub(
|
||||
r"(<downloadurl[^>]*>)https://github\.com/[^<]*(</downloadurl>)",
|
||||
rf"\g<1>{gh_url}\g<2>",
|
||||
block
|
||||
)
|
||||
else:
|
||||
# Remove any GitHub download URL for dev/alpha/beta
|
||||
block = re.sub(
|
||||
r"\n\s*<downloadurl[^>]*>https://github\.com/[^<]*</downloadurl>",
|
||||
"",
|
||||
block
|
||||
)
|
||||
|
||||
content = content.replace(original_block, block)
|
||||
|
||||
with open("updates.xml", "w") as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"Updated {xml_tag} channel: version={version}, sha={sha256[:16]}..., date={date}")
|
||||
PYEOF
|
||||
|
||||
- name: "Commit updates.xml to current branch and main"
|
||||
run: |
|
||||
if git diff --quiet updates.xml 2>/dev/null; then
|
||||
echo "No changes to updates.xml"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add updates.xml
|
||||
git commit -m "chore: update ${STABILITY} SHA-256 for ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
|
||||
# Set push URL with GA_TOKEN for authenticated pushes (branch protection requires jmiller)
|
||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
# Push to current branch
|
||||
git push || true
|
||||
|
||||
# Also update updates.xml on main via Gitea API (git push blocked by branch protection)
|
||||
if [ "$CURRENT_BRANCH" != "main" ]; then
|
||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
|
||||
# Get current file SHA on main (required for update)
|
||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty')
|
||||
|
||||
if [ -n "$FILE_SHA" ]; then
|
||||
# Base64-encode the updates.xml content from working tree (has updated SHA)
|
||||
CONTENT=$(base64 -w0 updates.xml)
|
||||
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/contents/updates.xml" \
|
||||
-d "$(jq -n \
|
||||
--arg content "$CONTENT" \
|
||||
--arg sha "$FILE_SHA" \
|
||||
--arg msg "chore: update ${STABILITY} channel to ${VERSION} on main [skip ci]" \
|
||||
--arg branch "main" \
|
||||
'{content: $content, sha: $sha, message: $msg, branch: $branch}'
|
||||
)")
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
||||
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
||||
echo "updates.xml synced to main via API (HTTP ${HTTP_CODE})"
|
||||
else
|
||||
echo "WARNING: failed to sync updates.xml to main (HTTP ${HTTP_CODE})"
|
||||
echo "$RESPONSE" | head -5
|
||||
fi
|
||||
else
|
||||
echo "WARNING: could not get file SHA for updates.xml on main"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||
TAG="${{ steps.meta.outputs.tag_name }}"
|
||||
|
||||
echo "### Release Created" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tag | \`${TAG}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Gitea | [Release](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${TAG}) |" >> $GITHUB_STEP_SUMMARY
|
||||
787
.github/workflows/repo_health.yml
vendored
Normal file
787
.github/workflows/repo_health.yml
vendored
Normal file
@@ -0,0 +1,787 @@
|
||||
# ============================================================================
|
||||
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# This file is part of a Moko Consulting project.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Validation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.github/workflows/repo_health.yml
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts.
|
||||
# NOTE: Field is user-managed.
|
||||
# ============================================================================
|
||||
|
||||
name: Repo Health
|
||||
|
||||
concurrency:
|
||||
group: repo-health-${{ github.repository }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
profile:
|
||||
description: 'Validation profile: all, release, scripts, or repo'
|
||||
required: true
|
||||
default: all
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- release
|
||||
- scripts
|
||||
- repo
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
# Release policy - Repository Variables Only
|
||||
RELEASE_REQUIRED_REPO_VARS: RS_FTP_PATH_SUFFIX
|
||||
RELEASE_OPTIONAL_REPO_VARS: DEV_FTP_SUFFIX
|
||||
|
||||
# Scripts governance policy
|
||||
# Note: directories listed without a trailing slash.
|
||||
SCRIPTS_REQUIRED_DIRS:
|
||||
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
|
||||
|
||||
# Repo health policy
|
||||
# Files are listed as-is; directories must end with a trailing slash.
|
||||
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.github/workflows/
|
||||
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
|
||||
REPO_DISALLOWED_DIRS:
|
||||
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
||||
|
||||
# Extended checks toggles
|
||||
EXTENDED_CHECKS: "true"
|
||||
|
||||
# File / directory variables (moved to top-level env)
|
||||
DOCS_INDEX: docs/docs-index.md
|
||||
SCRIPT_DIR: scripts
|
||||
WORKFLOWS_DIR: .github/workflows
|
||||
SHELLCHECK_PATTERN: '*.sh'
|
||||
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
access_check:
|
||||
name: Access control
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
outputs:
|
||||
allowed: ${{ steps.perm.outputs.allowed }}
|
||||
permission: ${{ steps.perm.outputs.permission }}
|
||||
|
||||
steps:
|
||||
- name: Check actor permission (admin only)
|
||||
id: perm
|
||||
run: |
|
||||
ACTOR="${{ github.actor }}"
|
||||
REPO="${{ github.repository }}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
GITEA_API="${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1"
|
||||
|
||||
PERMISSION="unknown"
|
||||
ALLOWED="false"
|
||||
METHOD=""
|
||||
|
||||
# Hardcoded authorized users
|
||||
if [ "$ACTOR" = "jmiller" ] || [ "$ACTOR" = "gitea-actions[bot]" ]; then
|
||||
PERMISSION="admin"
|
||||
ALLOWED="true"
|
||||
METHOD="hardcoded allowlist"
|
||||
else
|
||||
# Check via Gitea API
|
||||
RESULT=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||
"${GITEA_API}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}')
|
||||
PERMISSION=$(echo "$RESULT" | jq -r '.permission // "unknown"')
|
||||
if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "owner" ] || [ "$PERMISSION" = "maintain" ]; then
|
||||
ALLOWED="true"
|
||||
fi
|
||||
METHOD="Gitea collaborator API"
|
||||
fi
|
||||
|
||||
echo "permission=${PERMISSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "allowed=${ALLOWED}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
{
|
||||
echo "## 🔐 Access Authorization"
|
||||
echo ""
|
||||
echo "| Field | Value |"
|
||||
echo "|-------|-------|"
|
||||
echo "| **Actor** | \`${ACTOR}\` |"
|
||||
echo "| **Repository** | \`${REPO}\` |"
|
||||
echo "| **Permission** | \`${PERMISSION}\` |"
|
||||
echo "| **Method** | ${METHOD} |"
|
||||
echo "| **Authorized** | ${ALLOWED} |"
|
||||
echo "| **Trigger** | \`${{ github.event_name }}\` |"
|
||||
echo "| **Branch** | \`${GITHUB_REF#refs/heads/}\` |"
|
||||
echo ""
|
||||
if [ "$ALLOWED" = "true" ]; then
|
||||
echo "✅ ${ACTOR} authorized (${METHOD})"
|
||||
else
|
||||
echo "❌ ${ACTOR} is NOT authorized. Requires admin or maintain role."
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Deny execution when not permitted
|
||||
if: ${{ steps.perm.outputs.allowed != 'true' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 1
|
||||
|
||||
release_config:
|
||||
name: Release configuration
|
||||
needs: access_check
|
||||
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Guardrails release vars
|
||||
env:
|
||||
PROFILE_RAW: ${{ github.event.inputs.profile }}
|
||||
RS_FTP_PATH_SUFFIX: ${{ vars.RS_FTP_PATH_SUFFIX }}
|
||||
DEV_FTP_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
profile="${PROFILE_RAW:-all}"
|
||||
case "${profile}" in
|
||||
all|release|scripts|repo) ;;
|
||||
*)
|
||||
printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then
|
||||
{
|
||||
printf '%s\n' '### Release configuration (Repository Variables)'
|
||||
printf '%s\n' "Profile: ${profile}"
|
||||
printf '%s\n' 'Status: SKIPPED'
|
||||
printf '%s\n' 'Reason: profile excludes release validation'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
IFS=',' read -r -a required <<< "${RELEASE_REQUIRED_REPO_VARS}"
|
||||
IFS=',' read -r -a optional <<< "${RELEASE_OPTIONAL_REPO_VARS}"
|
||||
|
||||
missing=()
|
||||
missing_optional=()
|
||||
|
||||
for k in "${required[@]}"; do
|
||||
v="${!k:-}"
|
||||
[ -z "${v}" ] && missing+=("${k}")
|
||||
done
|
||||
|
||||
for k in "${optional[@]}"; do
|
||||
v="${!k:-}"
|
||||
[ -z "${v}" ] && missing_optional+=("${k}")
|
||||
done
|
||||
|
||||
{
|
||||
printf '%s\n' '### Release configuration (Repository Variables)'
|
||||
printf '%s\n' "Profile: ${profile}"
|
||||
printf '%s\n' '| Variable | Status |'
|
||||
printf '%s\n' '|---|---|'
|
||||
printf '%s\n' "| RS_FTP_PATH_SUFFIX | ${RS_FTP_PATH_SUFFIX:-NOT SET} |"
|
||||
printf '%s\n' "| DEV_FTP_SUFFIX | ${DEV_FTP_SUFFIX:-NOT SET} |"
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
|
||||
if [ "${#missing_optional[@]}" -gt 0 ]; then
|
||||
{
|
||||
printf '%s\n' '### Missing optional repository variables'
|
||||
for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
|
||||
if [ "${#missing[@]}" -gt 0 ]; then
|
||||
{
|
||||
printf '%s\n' '### Missing required repository variables'
|
||||
for m in "${missing[@]}"; do printf '%s\n' "- ${m}"; done
|
||||
printf '%s\n' 'ERROR: Guardrails failed. Missing required repository variables.'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
printf '%s\n' '### Repository variables validation result'
|
||||
printf '%s\n' 'Status: OK'
|
||||
printf '%s\n' 'All required repository variables present.'
|
||||
printf '%s\n' ''
|
||||
printf '%s\n' '**Note**: Organization secrets (RS_FTP_HOST, RS_FTP_USER, etc.) are validated at deployment time, not in repository health checks.'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
|
||||
scripts_governance:
|
||||
name: Scripts governance
|
||||
needs: access_check
|
||||
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Scripts folder checks
|
||||
env:
|
||||
PROFILE_RAW: ${{ github.event.inputs.profile }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
profile="${PROFILE_RAW:-all}"
|
||||
case "${profile}" in
|
||||
all|release|scripts|repo) ;;
|
||||
*)
|
||||
printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "${profile}" = 'release' ] || [ "${profile}" = 'repo' ]; then
|
||||
{
|
||||
printf '%s\n' '### Scripts governance'
|
||||
printf '%s\n' "Profile: ${profile}"
|
||||
printf '%s\n' 'Status: SKIPPED'
|
||||
printf '%s\n' 'Reason: profile excludes scripts governance'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -d "${SCRIPT_DIR}" ]; then
|
||||
{
|
||||
printf '%s\n' '### Scripts governance'
|
||||
printf '%s\n' 'Status: OK (advisory)'
|
||||
printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"
|
||||
IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}"
|
||||
|
||||
missing_dirs=()
|
||||
unapproved_dirs=()
|
||||
|
||||
for d in "${required_dirs[@]}"; do
|
||||
req="${d%/}"
|
||||
[ ! -d "${req}" ] && missing_dirs+=("${req}/")
|
||||
done
|
||||
|
||||
while IFS= read -r d; do
|
||||
allowed=false
|
||||
for a in "${allowed_dirs[@]}"; do
|
||||
a_norm="${a%/}"
|
||||
[ "${d%/}" = "${a_norm}" ] && allowed=true
|
||||
done
|
||||
[ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/")
|
||||
done < <(find "${SCRIPT_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##')
|
||||
|
||||
{
|
||||
printf '%s\n' '### Scripts governance'
|
||||
printf '%s\n' "Profile: ${profile}"
|
||||
printf '%s\n' '| Area | Status | Notes |'
|
||||
printf '%s\n' '|---|---|---|'
|
||||
|
||||
if [ "${#missing_dirs[@]}" -gt 0 ]; then
|
||||
printf '%s\n' '| Required directories | Warning | Missing required subfolders |'
|
||||
else
|
||||
printf '%s\n' '| Required directories | OK | All required subfolders present |'
|
||||
fi
|
||||
|
||||
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
|
||||
printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
|
||||
else
|
||||
printf '%s\n' '| Directory policy | OK | No unapproved directories |'
|
||||
fi
|
||||
|
||||
printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |'
|
||||
printf '\n'
|
||||
|
||||
if [ "${#missing_dirs[@]}" -gt 0 ]; then
|
||||
printf '%s\n' 'Missing required script directories:'
|
||||
for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done
|
||||
printf '\n'
|
||||
else
|
||||
printf '%s\n' 'Missing required script directories: none.'
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
|
||||
printf '%s\n' 'Unapproved script directories detected:'
|
||||
for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done
|
||||
printf '\n'
|
||||
else
|
||||
printf '%s\n' 'Unapproved script directories detected: none.'
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
printf '%s\n' 'Scripts governance completed in advisory mode.'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
|
||||
repo_health:
|
||||
name: Repository health
|
||||
needs: access_check
|
||||
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Repository health checks
|
||||
env:
|
||||
PROFILE_RAW: ${{ github.event.inputs.profile }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
profile="${PROFILE_RAW:-all}"
|
||||
case "${profile}" in
|
||||
all|release|scripts|repo) ;;
|
||||
*)
|
||||
printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "${profile}" = 'release' ] || [ "${profile}" = 'scripts' ]; then
|
||||
{
|
||||
printf '%s\n' '### Repository health'
|
||||
printf '%s\n' "Profile: ${profile}"
|
||||
printf '%s\n' 'Status: SKIPPED'
|
||||
printf '%s\n' 'Reason: profile excludes repository health'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Source directory: src/ or htdocs/ (either is valid)
|
||||
if [ -d "src" ]; then
|
||||
SOURCE_DIR="src"
|
||||
elif [ -d "htdocs" ]; then
|
||||
SOURCE_DIR="htdocs"
|
||||
else
|
||||
missing_required+=("src/ or htdocs/ (source directory required)")
|
||||
fi
|
||||
|
||||
IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}"
|
||||
IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
|
||||
IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"
|
||||
IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES}"
|
||||
|
||||
missing_required=()
|
||||
missing_optional=()
|
||||
|
||||
for item in "${required_artifacts[@]}"; do
|
||||
if printf '%s' "${item}" | grep -q '/$'; then
|
||||
d="${item%/}"
|
||||
[ ! -d "${d}" ] && missing_required+=("${item}")
|
||||
else
|
||||
[ ! -f "${item}" ] && missing_required+=("${item}")
|
||||
fi
|
||||
done
|
||||
|
||||
# Optional entries: handle files and directories (trailing slash indicates dir)
|
||||
for f in "${optional_files[@]}"; do
|
||||
if printf '%s' "${f}" | grep -q '/$'; then
|
||||
d="${f%/}"
|
||||
[ ! -d "${d}" ] && missing_optional+=("${f}")
|
||||
else
|
||||
[ ! -f "${f}" ] && missing_optional+=("${f}")
|
||||
fi
|
||||
done
|
||||
|
||||
for d in "${disallowed_dirs[@]}"; do
|
||||
d_norm="${d%/}"
|
||||
[ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)")
|
||||
done
|
||||
|
||||
for f in "${disallowed_files[@]}"; do
|
||||
[ -f "${f}" ] && missing_required+=("${f} (disallowed)")
|
||||
done
|
||||
|
||||
git fetch origin --prune
|
||||
|
||||
dev_paths=()
|
||||
dev_branches=()
|
||||
|
||||
# Look for remote branches matching origin/dev*.
|
||||
# A plain origin/dev is considered invalid; we require dev/<something> branches.
|
||||
while IFS= read -r b; do
|
||||
name="${b#origin/}"
|
||||
if [ "${name}" = 'dev' ]; then
|
||||
dev_branches+=("${name}")
|
||||
else
|
||||
dev_paths+=("${name}")
|
||||
fi
|
||||
done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//')
|
||||
|
||||
# If there are no dev/* branches, fail the guardrail.
|
||||
if [ "${#dev_paths[@]}" -eq 0 ]; then
|
||||
missing_required+=("dev/* branch (e.g. dev/01.00.00)")
|
||||
fi
|
||||
|
||||
# If a plain dev branch exists (origin/dev), flag it as invalid.
|
||||
if [ "${#dev_branches[@]}" -gt 0 ]; then
|
||||
missing_required+=("invalid branch dev (must be dev/<version>)")
|
||||
fi
|
||||
|
||||
content_warnings=()
|
||||
|
||||
if [ -f 'CHANGELOG.md' ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then
|
||||
content_warnings+=("CHANGELOG.md missing '# Changelog' header")
|
||||
fi
|
||||
|
||||
if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then
|
||||
content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)")
|
||||
fi
|
||||
|
||||
if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then
|
||||
content_warnings+=("LICENSE does not look like a GPL text")
|
||||
fi
|
||||
|
||||
if [ -f 'README.md' ] && ! grep -qiE 'moko|Moko' README.md; then
|
||||
content_warnings+=("README.md missing expected brand keyword")
|
||||
fi
|
||||
|
||||
export PROFILE_RAW="${profile}"
|
||||
export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")"
|
||||
export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")"
|
||||
export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")"
|
||||
|
||||
report_json="$(python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
|
||||
profile = os.environ.get('PROFILE_RAW') or 'all'
|
||||
|
||||
missing_required = os.environ.get('MISSING_REQUIRED', '').splitlines() if os.environ.get('MISSING_REQUIRED') else []
|
||||
missing_optional = os.environ.get('MISSING_OPTIONAL', '').splitlines() if os.environ.get('MISSING_OPTIONAL') else []
|
||||
content_warnings = os.environ.get('CONTENT_WARNINGS', '').splitlines() if os.environ.get('CONTENT_WARNINGS') else []
|
||||
|
||||
out = {
|
||||
'profile': profile,
|
||||
'missing_required': [x for x in missing_required if x],
|
||||
'missing_optional': [x for x in missing_optional if x],
|
||||
'content_warnings': [x for x in content_warnings if x],
|
||||
}
|
||||
|
||||
print(json.dumps(out, indent=2))
|
||||
PY
|
||||
)"
|
||||
|
||||
{
|
||||
printf '%s\n' '### Repository health'
|
||||
printf '%s\n' "Profile: ${profile}"
|
||||
printf '%s\n' '| Metric | Value |'
|
||||
printf '%s\n' '|---|---|'
|
||||
printf '%s\n' "| Missing required | ${#missing_required[@]} |"
|
||||
printf '%s\n' "| Missing optional | ${#missing_optional[@]} |"
|
||||
printf '%s\n' "| Content warnings | ${#content_warnings[@]} |"
|
||||
printf '\n'
|
||||
|
||||
printf '%s\n' '### Guardrails report (JSON)'
|
||||
printf '%s\n' '```json'
|
||||
printf '%s\n' "${report_json}"
|
||||
printf '%s\n' '```'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
|
||||
if [ "${#missing_required[@]}" -gt 0 ]; then
|
||||
{
|
||||
printf '%s\n' '### Missing required repo artifacts'
|
||||
for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done
|
||||
printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${#missing_optional[@]}" -gt 0 ]; then
|
||||
{
|
||||
printf '%s\n' '### Missing optional repo artifacts'
|
||||
for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
|
||||
if [ "${#content_warnings[@]}" -gt 0 ]; then
|
||||
{
|
||||
printf '%s\n' '### Repo content warnings'
|
||||
for m in "${content_warnings[@]}"; do printf '%s\n' "- ${m}"; done
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
|
||||
# ── Joomla-specific checks ───────────────────────────────────────
|
||||
joomla_findings=()
|
||||
|
||||
# XML manifest: find any XML file containing <extension
|
||||
MANIFEST="$(find . -maxdepth 2 -name '*.xml' -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)"
|
||||
if [ -z "${MANIFEST}" ]; then
|
||||
joomla_findings+=("Joomla XML manifest not found (no *.xml with <extension> tag)")
|
||||
else
|
||||
# Check <version> tag exists
|
||||
if ! grep -qP '<version>' "${MANIFEST}"; then
|
||||
joomla_findings+=("XML manifest: <version> tag missing")
|
||||
fi
|
||||
# Check extension type attribute
|
||||
if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then
|
||||
joomla_findings+=("XML manifest: type attribute missing or invalid")
|
||||
fi
|
||||
# Check <name> tag
|
||||
if ! grep -qP '<name>' "${MANIFEST}"; then
|
||||
joomla_findings+=("XML manifest: <name> tag missing")
|
||||
fi
|
||||
# Check <author> tag
|
||||
if ! grep -qP '<author>' "${MANIFEST}"; then
|
||||
joomla_findings+=("XML manifest: <author> tag missing")
|
||||
fi
|
||||
# Check <namespace> for Joomla 5+
|
||||
if ! grep -qP '<namespace' "${MANIFEST}"; then
|
||||
joomla_findings+=("XML manifest: <namespace> missing (required for Joomla 5+)")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Language files: check for at least one .ini file
|
||||
INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)"
|
||||
if [ "${INI_COUNT}" -eq 0 ]; then
|
||||
joomla_findings+=("No .ini language files found")
|
||||
fi
|
||||
|
||||
# updates.xml must exist in root (Joomla update server)
|
||||
if [ ! -f 'updates.xml' ]; then
|
||||
joomla_findings+=("updates.xml missing in root (required for Joomla update server)")
|
||||
fi
|
||||
|
||||
# index.html files for directory listing protection
|
||||
INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site")
|
||||
for dir in "${INDEX_DIRS[@]}"; do
|
||||
if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then
|
||||
joomla_findings+=("${dir}/index.html missing (directory listing protection)")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#joomla_findings[@]}" -gt 0 ]; then
|
||||
{
|
||||
printf '%s\n' '### Joomla extension checks'
|
||||
printf '%s\n' '| Check | Status |'
|
||||
printf '%s\n' '|---|---|'
|
||||
for f in "${joomla_findings[@]}"; do
|
||||
printf '%s\n' "| ${f} | Warning |"
|
||||
done
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
else
|
||||
{
|
||||
printf '%s\n' '### Joomla extension checks'
|
||||
printf '%s\n' 'All Joomla-specific checks passed.'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
|
||||
extended_enabled="${EXTENDED_CHECKS:-true}"
|
||||
extended_findings=()
|
||||
|
||||
if [ "${extended_enabled}" = 'true' ]; then
|
||||
# CODEOWNERS presence
|
||||
if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then
|
||||
:
|
||||
else
|
||||
extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)")
|
||||
fi
|
||||
|
||||
# Workflow pinning advisory: flag uses @main/@master
|
||||
if ls "${WORKFLOWS_DIR}"/*.yml >/dev/null 2>&1 || ls "${WORKFLOWS_DIR}"/*.yaml >/dev/null 2>&1; then
|
||||
bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' "${WORKFLOWS_DIR}" 2>/dev/null || true)"
|
||||
if [ -n "${bad_refs}" ]; then
|
||||
extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt")
|
||||
{
|
||||
printf '%s\n' '### Workflow pinning advisory'
|
||||
printf '%s\n' 'Found uses: entries pinned to main/master:'
|
||||
printf '%s\n' '```'
|
||||
printf '%s\n' "${bad_refs}"
|
||||
printf '%s\n' '```'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Docs index link integrity (docs/docs-index.md)
|
||||
if [ -f "${DOCS_INDEX}" ]; then
|
||||
missing_links="$(python3 - <<'PY'
|
||||
import os
|
||||
import re
|
||||
|
||||
idx = os.environ.get('DOCS_INDEX', 'docs/docs-index.md')
|
||||
base = os.getcwd()
|
||||
|
||||
bad = []
|
||||
pat = re.compile(r'\[[^\]]+\]\(([^)]+)\)')
|
||||
|
||||
with open(idx, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
for m in pat.findall(line):
|
||||
link = m.strip()
|
||||
if link.startswith('http://') or link.startswith('https://') or link.startswith('#') or link.startswith('mailto:'):
|
||||
continue
|
||||
if link.startswith('/'):
|
||||
rel = link.lstrip('/')
|
||||
else:
|
||||
rel = os.path.normpath(os.path.join(os.path.dirname(idx), link))
|
||||
rel = rel.split('#', 1)[0]
|
||||
rel = rel.split('?', 1)[0]
|
||||
if not rel:
|
||||
continue
|
||||
p = os.path.join(base, rel)
|
||||
if not os.path.exists(p):
|
||||
bad.append(rel)
|
||||
|
||||
print('\n'.join(sorted(set(bad))))
|
||||
PY
|
||||
)"
|
||||
if [ -n "${missing_links}" ]; then
|
||||
extended_findings+=("docs/docs-index.md contains broken relative links")
|
||||
{
|
||||
printf '%s\n' '### Docs index link integrity'
|
||||
printf '%s\n' 'Broken relative links:'
|
||||
while IFS= read -r l; do [ -n "${l}" ] && printf '%s\n' "- ${l}"; done <<< "${missing_links}"
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ShellCheck advisory
|
||||
if [ -d "${SCRIPT_DIR}" ]; then
|
||||
if ! command -v shellcheck >/dev/null 2>&1; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y shellcheck >/dev/null
|
||||
fi
|
||||
|
||||
sc_out=''
|
||||
while IFS= read -r shf; do
|
||||
[ -z "${shf}" ] && continue
|
||||
out_one="$(shellcheck -S warning -x "${shf}" 2>/dev/null || true)"
|
||||
if [ -n "${out_one}" ]; then
|
||||
sc_out="${sc_out}${out_one}\n"
|
||||
fi
|
||||
done < <(find "${SCRIPT_DIR}" -type f -name "${SHELLCHECK_PATTERN}" 2>/dev/null | sort)
|
||||
|
||||
if [ -n "${sc_out}" ]; then
|
||||
extended_findings+=("ShellCheck warnings detected (advisory)")
|
||||
sc_head="$(printf '%s' "${sc_out}" | head -n 200)"
|
||||
{
|
||||
printf '%s\n' '### ShellCheck (advisory)'
|
||||
printf '%s\n' '```'
|
||||
printf '%s\n' "${sc_head}"
|
||||
printf '%s\n' '```'
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# SPDX header advisory for common source types
|
||||
spdx_missing=()
|
||||
IFS=',' read -r -a spdx_globs <<< "${SPDX_FILE_GLOBS}"
|
||||
spdx_args=()
|
||||
for g in "${spdx_globs[@]}"; do spdx_args+=("${g}"); done
|
||||
|
||||
while IFS= read -r f; do
|
||||
[ -z "${f}" ] && continue
|
||||
if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then
|
||||
spdx_missing+=("${f}")
|
||||
fi
|
||||
done < <(git ls-files "${spdx_args[@]}" 2>/dev/null || true)
|
||||
|
||||
if [ "${#spdx_missing[@]}" -gt 0 ]; then
|
||||
extended_findings+=("SPDX header missing in some tracked files (advisory)")
|
||||
{
|
||||
printf '%s\n' '### SPDX header advisory'
|
||||
printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):'
|
||||
for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
|
||||
# Git hygiene advisory: branches older than 180 days (remote)
|
||||
stale_cutoff_days=180
|
||||
stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 [...]
|
||||
if [ -n "${stale_branches}" ]; then
|
||||
extended_findings+=("Stale remote branches detected (advisory)")
|
||||
{
|
||||
printf '%s\n' '### Git hygiene advisory'
|
||||
printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):"
|
||||
while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}"
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
fi
|
||||
|
||||
{
|
||||
printf '%s\n' '### Guardrails coverage matrix'
|
||||
printf '%s\n' '| Domain | Status | Notes |'
|
||||
printf '%s\n' '|---|---|---|'
|
||||
printf '%s\n' '| Access control | OK | Admin-only execution gate |'
|
||||
printf '%s\n' '| Release variables | OK | Repository variables validation |'
|
||||
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
|
||||
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
|
||||
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
|
||||
if [ "${extended_enabled}" = 'true' ]; then
|
||||
if [ "${#extended_findings[@]}" -gt 0 ]; then
|
||||
printf '%s\n' '| Extended checks | Warning | See extended findings below |'
|
||||
else
|
||||
printf '%s\n' '| Extended checks | OK | No findings |'
|
||||
fi
|
||||
else
|
||||
printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |'
|
||||
fi
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
|
||||
if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then
|
||||
{
|
||||
printf '%s\n' '### Extended findings (advisory)'
|
||||
for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
|
||||
printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}"
|
||||
525
.github/workflows/repository-cleanup.yml
vendored
Normal file
525
.github/workflows/repository-cleanup.yml
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# This file is part of a Moko Consulting project.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/shared/repository-cleanup.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
|
||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/repository-cleanup.yml in all governed repos.
|
||||
# Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch.
|
||||
|
||||
name: Repository Cleanup
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 1,15 * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
reset_labels:
|
||||
description: 'Delete ALL existing labels and recreate the standard set'
|
||||
type: boolean
|
||||
default: false
|
||||
clean_branches:
|
||||
description: 'Delete old chore/sync-mokostandards-* branches'
|
||||
type: boolean
|
||||
default: true
|
||||
clean_workflows:
|
||||
description: 'Delete orphaned workflow runs (cancelled, stale)'
|
||||
type: boolean
|
||||
default: true
|
||||
clean_logs:
|
||||
description: 'Delete workflow run logs older than 30 days'
|
||||
type: boolean
|
||||
default: true
|
||||
fix_templates:
|
||||
description: 'Strip copyright comment blocks from issue templates'
|
||||
type: boolean
|
||||
default: true
|
||||
rebuild_indexes:
|
||||
description: 'Rebuild docs/ index files'
|
||||
type: boolean
|
||||
default: true
|
||||
delete_closed_issues:
|
||||
description: 'Delete issues that have been closed for more than 30 days'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Repository Maintenance
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.GA_TOKEN || github.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check actor permission
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
ACTOR="${{ github.actor }}"
|
||||
# Schedule triggers use gitea-actions[bot]
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
echo "✅ Scheduled run — authorized"
|
||||
exit 0
|
||||
fi
|
||||
AUTHORIZED_USERS="jmiller gitea-actions[bot]"
|
||||
for user in $AUTHORIZED_USERS; do
|
||||
if [ "$ACTOR" = "$user" ]; then
|
||||
echo "✅ ${ACTOR} authorized"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
PERMISSION=$(gh api "repos/${{ github.repository }}/collaborators/${ACTOR}/permission" \
|
||||
2>/dev/null | jq -r '.permission')
|
||||
case "$PERMISSION" in
|
||||
admin|maintain) echo "✅ ${ACTOR} has ${PERMISSION}" ;;
|
||||
*) echo "❌ Admin or maintain required"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# ── Determine which tasks to run ─────────────────────────────────────
|
||||
# On schedule: run all tasks with safe defaults (labels NOT reset)
|
||||
# On dispatch: use input toggles
|
||||
- name: Set task flags
|
||||
id: tasks
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
echo "reset_labels=false" >> $GITHUB_OUTPUT
|
||||
echo "clean_branches=true" >> $GITHUB_OUTPUT
|
||||
echo "clean_workflows=true" >> $GITHUB_OUTPUT
|
||||
echo "clean_logs=true" >> $GITHUB_OUTPUT
|
||||
echo "fix_templates=true" >> $GITHUB_OUTPUT
|
||||
echo "rebuild_indexes=true" >> $GITHUB_OUTPUT
|
||||
echo "delete_closed_issues=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "reset_labels=${{ inputs.reset_labels }}" >> $GITHUB_OUTPUT
|
||||
echo "clean_branches=${{ inputs.clean_branches }}" >> $GITHUB_OUTPUT
|
||||
echo "clean_workflows=${{ inputs.clean_workflows }}" >> $GITHUB_OUTPUT
|
||||
echo "clean_logs=${{ inputs.clean_logs }}" >> $GITHUB_OUTPUT
|
||||
echo "fix_templates=${{ inputs.fix_templates }}" >> $GITHUB_OUTPUT
|
||||
echo "rebuild_indexes=${{ inputs.rebuild_indexes }}" >> $GITHUB_OUTPUT
|
||||
echo "delete_closed_issues=${{ inputs.delete_closed_issues }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# ── DELETE RETIRED WORKFLOWS (always runs) ────────────────────────────
|
||||
- name: Delete retired workflow files
|
||||
run: |
|
||||
echo "## 🗑️ Retired Workflow Cleanup" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
RETIRED=(
|
||||
".github/workflows/build.yml"
|
||||
".github/workflows/code-quality.yml"
|
||||
".github/workflows/release-cycle.yml"
|
||||
".github/workflows/release-pipeline.yml"
|
||||
".github/workflows/branch-cleanup.yml"
|
||||
".github/workflows/auto-update-changelog.yml"
|
||||
".github/workflows/enterprise-issue-manager.yml"
|
||||
".github/workflows/flush-actions-cache.yml"
|
||||
".github/workflows/mokostandards-script-runner.yml"
|
||||
".github/workflows/unified-ci.yml"
|
||||
".github/workflows/unified-platform-testing.yml"
|
||||
".github/workflows/reusable-build.yml"
|
||||
".github/workflows/reusable-ci-validation.yml"
|
||||
".github/workflows/reusable-deploy.yml"
|
||||
".github/workflows/reusable-php-quality.yml"
|
||||
".github/workflows/reusable-platform-testing.yml"
|
||||
".github/workflows/reusable-project-detector.yml"
|
||||
".github/workflows/reusable-release.yml"
|
||||
".github/workflows/reusable-script-executor.yml"
|
||||
".github/workflows/rebuild-docs-indexes.yml"
|
||||
".github/workflows/setup-project-v2.yml"
|
||||
".github/workflows/sync-docs-to-project.yml"
|
||||
".github/workflows/release.yml"
|
||||
".github/workflows/sync-changelogs.yml"
|
||||
".github/workflows/version_branch.yml"
|
||||
"update.json"
|
||||
".github/workflows/auto-version-branch.yml"
|
||||
".github/workflows/publish-to-mokodolibarr.yml"
|
||||
".github/workflows/ci.yml"
|
||||
".github/workflows/deploy-rs.yml"
|
||||
"sftp-config.json"
|
||||
"sftp-config.json.template"
|
||||
"scripts/sftp-config"
|
||||
)
|
||||
|
||||
DELETED=0
|
||||
for wf in "${RETIRED[@]}"; do
|
||||
if [ -f "$wf" ]; then
|
||||
git rm "$wf" 2>/dev/null || rm -f "$wf"
|
||||
echo " Deleted: \`$(basename $wf)\`" >> $GITHUB_STEP_SUMMARY
|
||||
DELETED=$((DELETED+1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$DELETED" -gt 0 ]; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add -A
|
||||
git commit -m "chore: delete ${DELETED} retired workflow file(s) [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
echo "✅ ${DELETED} retired workflow(s) deleted" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "✅ No retired workflows found" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ── LABEL RESET ──────────────────────────────────────────────────────
|
||||
- name: Reset labels to standard set
|
||||
if: steps.tasks.outputs.reset_labels == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
REPO="${{ github.repository }}"
|
||||
echo "## 🏷️ Label Reset" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/labels?per_page=100" 2>/dev/null | jq -r '.[].name' | while read -r label; do
|
||||
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$label', safe=''))")
|
||||
gh api -X DELETE "repos/${REPO}/labels/${ENCODED}" --silent 2>/dev/null || true
|
||||
done
|
||||
|
||||
while IFS='|' read -r name color description; do
|
||||
[ -z "$name" ] && continue
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/labels" 2>/dev/null \
|
||||
-f name="$name" -f color="$color" -f description="$description" \
|
||||
--silent 2>/dev/null || true
|
||||
done << 'LABELS'
|
||||
joomla|7F52FF|Joomla extension or component
|
||||
dolibarr|FF6B6B|Dolibarr module or extension
|
||||
generic|808080|Generic project or library
|
||||
php|4F5D95|PHP code changes
|
||||
javascript|F7DF1E|JavaScript code changes
|
||||
typescript|3178C6|TypeScript code changes
|
||||
python|3776AB|Python code changes
|
||||
css|1572B6|CSS/styling changes
|
||||
html|E34F26|HTML template changes
|
||||
documentation|0075CA|Documentation changes
|
||||
ci-cd|000000|CI/CD pipeline changes
|
||||
docker|2496ED|Docker configuration changes
|
||||
tests|00FF00|Test suite changes
|
||||
security|FF0000|Security-related changes
|
||||
dependencies|0366D6|Dependency updates
|
||||
config|F9D0C4|Configuration file changes
|
||||
build|FFA500|Build system changes
|
||||
automation|8B4513|Automated processes or scripts
|
||||
mokostandards|B60205|MokoStandards compliance
|
||||
needs-review|FBCA04|Awaiting code review
|
||||
work-in-progress|D93F0B|Work in progress, not ready for merge
|
||||
breaking-change|D73A4A|Breaking API or functionality change
|
||||
priority: critical|B60205|Critical priority, must be addressed immediately
|
||||
priority: high|D93F0B|High priority
|
||||
priority: medium|FBCA04|Medium priority
|
||||
priority: low|0E8A16|Low priority
|
||||
type: bug|D73A4A|Something isn't working
|
||||
type: feature|A2EEEF|New feature or request
|
||||
type: enhancement|84B6EB|Enhancement to existing feature
|
||||
type: refactor|F9D0C4|Code refactoring
|
||||
type: chore|FEF2C0|Maintenance tasks
|
||||
type: version|0E8A16|Version-related change
|
||||
status: pending|FBCA04|Pending action or decision
|
||||
status: in-progress|0E8A16|Currently being worked on
|
||||
status: blocked|B60205|Blocked by another issue or dependency
|
||||
status: on-hold|D4C5F9|Temporarily on hold
|
||||
status: wontfix|FFFFFF|This will not be worked on
|
||||
size/xs|C5DEF5|Extra small change (1-10 lines)
|
||||
size/s|6FD1E2|Small change (11-30 lines)
|
||||
size/m|F9DD72|Medium change (31-100 lines)
|
||||
size/l|FFA07A|Large change (101-300 lines)
|
||||
size/xl|FF6B6B|Extra large change (301-1000 lines)
|
||||
size/xxl|B60205|Extremely large change (1000+ lines)
|
||||
health: excellent|0E8A16|Health score 90-100
|
||||
health: good|FBCA04|Health score 70-89
|
||||
health: fair|FFA500|Health score 50-69
|
||||
health: poor|FF6B6B|Health score below 50
|
||||
standards-update|B60205|MokoStandards sync update
|
||||
standards-drift|FBCA04|Repository drifted from MokoStandards
|
||||
sync-report|0075CA|Bulk sync run report
|
||||
sync-failure|D73A4A|Bulk sync failure requiring attention
|
||||
push-failure|D73A4A|File push failure requiring attention
|
||||
health-check|0E8A16|Repository health check results
|
||||
version-drift|FFA500|Version mismatch detected
|
||||
deploy-failure|CC0000|Automated deploy failure tracking
|
||||
template-validation-failure|D73A4A|Template workflow validation failure
|
||||
version|0E8A16|Version bump or release
|
||||
LABELS
|
||||
|
||||
echo "✅ Standard labels created" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── BRANCH CLEANUP ───────────────────────────────────────────────────
|
||||
- name: Delete old sync branches
|
||||
if: steps.tasks.outputs.clean_branches == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
REPO="${{ github.repository }}"
|
||||
CURRENT="chore/sync-mokostandards-v04.05"
|
||||
echo "## 🌿 Branch Cleanup" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
FOUND=false
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/branches?per_page=100" | jq -r '.[].name' 2>/dev/null | \
|
||||
grep "^chore/sync-mokostandards" | \
|
||||
grep -v "^${CURRENT}$" | while read -r branch; do
|
||||
gh pr list --repo "$REPO" --head "$branch" --state open --json number 2>/dev/null | jq -r '.[].number' | while read -r pr; do
|
||||
gh pr close "$pr" --repo "$REPO" --comment "Superseded by \`${CURRENT}\`" 2>/dev/null || true
|
||||
echo " Closed PR #${pr}" >> $GITHUB_STEP_SUMMARY
|
||||
done
|
||||
gh api -X DELETE "repos/${REPO}/git/refs/heads/${branch}" --silent 2>/dev/null || true
|
||||
echo " Deleted: \`${branch}\`" >> $GITHUB_STEP_SUMMARY
|
||||
FOUND=true
|
||||
done
|
||||
|
||||
if [ "$FOUND" != "true" ]; then
|
||||
echo "✅ No old sync branches found" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ── WORKFLOW RUN CLEANUP ─────────────────────────────────────────────
|
||||
- name: Clean up workflow runs
|
||||
if: steps.tasks.outputs.clean_workflows == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
REPO="${{ github.repository }}"
|
||||
echo "## 🔄 Workflow Run Cleanup" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
DELETED=0
|
||||
# Delete cancelled and stale workflow runs
|
||||
for status in cancelled stale; do
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs?status=${status}&per_page=100" 2>/dev/null \
|
||||
2>/dev/null | jq -r '.workflow_runs[].id' | while read -r run_id; do
|
||||
gh api -X DELETE "repos/${REPO}/actions/runs/${run_id}" --silent 2>/dev/null || true
|
||||
DELETED=$((DELETED+1))
|
||||
done
|
||||
done
|
||||
|
||||
echo "✅ Cleaned cancelled/stale workflow runs" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── LOG CLEANUP ──────────────────────────────────────────────────────
|
||||
- name: Delete old workflow run logs
|
||||
if: steps.tasks.outputs.clean_logs == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
REPO="${{ github.repository }}"
|
||||
CUTOFF=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||
echo "## 📋 Log Cleanup" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Deleting logs older than: ${CUTOFF}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
DELETED=0
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/actions/runs?created=<${CUTOFF}&per_page=100" 2>/dev/null \
|
||||
2>/dev/null | jq -r '.workflow_runs[].id' | while read -r run_id; do
|
||||
gh api -X DELETE "repos/${REPO}/actions/runs/${run_id}/logs" --silent 2>/dev/null || true
|
||||
DELETED=$((DELETED+1))
|
||||
done
|
||||
|
||||
echo "✅ Cleaned old workflow run logs" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── ISSUE TEMPLATE FIX ──────────────────────────────────────────────
|
||||
- name: Strip copyright headers from issue templates
|
||||
if: steps.tasks.outputs.fix_templates == 'true'
|
||||
run: |
|
||||
echo "## 📋 Issue Template Cleanup" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
FIXED=0
|
||||
for f in .github/ISSUE_TEMPLATE/*.md; do
|
||||
[ -f "$f" ] || continue
|
||||
if grep -q '^<!--$' "$f"; then
|
||||
sed -i '/^<!--$/,/^-->$/d' "$f"
|
||||
echo " Cleaned: \`$(basename $f)\`" >> $GITHUB_STEP_SUMMARY
|
||||
FIXED=$((FIXED+1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$FIXED" -gt 0 ]; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add .github/ISSUE_TEMPLATE/
|
||||
git commit -m "fix: strip copyright comment blocks from issue templates [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
echo "✅ ${FIXED} template(s) cleaned and committed" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "✅ No templates need cleaning" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ── REBUILD DOC INDEXES ─────────────────────────────────────────────
|
||||
- name: Rebuild docs/ index files
|
||||
if: steps.tasks.outputs.rebuild_indexes == 'true'
|
||||
run: |
|
||||
echo "## 📚 Documentation Index Rebuild" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ ! -d "docs" ]; then
|
||||
echo "⏭️ No docs/ directory — skipping" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
UPDATED=0
|
||||
# Generate index.md for each docs/ subdirectory
|
||||
find docs -type d | while read -r dir; do
|
||||
INDEX="${dir}/index.md"
|
||||
FILES=$(find "$dir" -maxdepth 1 -name "*.md" ! -name "index.md" -printf "- [%f](./%f)\n" 2>/dev/null | sort)
|
||||
if [ -z "$FILES" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
cat > "$INDEX" << INDEXEOF
|
||||
# $(basename "$dir")
|
||||
|
||||
## Documents
|
||||
|
||||
${FILES}
|
||||
|
||||
---
|
||||
*Auto-generated by repository-cleanup workflow*
|
||||
INDEXEOF
|
||||
# Dedent
|
||||
sed -i 's/^ //' "$INDEX"
|
||||
UPDATED=$((UPDATED+1))
|
||||
done
|
||||
|
||||
if [ "$UPDATED" -gt 0 ]; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add docs/
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "docs: rebuild documentation indexes [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
echo "✅ ${UPDATED} index file(s) rebuilt and committed" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "✅ All indexes already up to date" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
else
|
||||
echo "✅ No indexes to rebuild" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ── VERSION DRIFT DETECTION ──────────────────────────────────────────
|
||||
- name: Check for version drift
|
||||
run: |
|
||||
echo "## 📦 Version Drift Check" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ ! -f "README.md" ]; then
|
||||
echo "⏭️ No README.md — skipping" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md 2>/dev/null | head -1)
|
||||
if [ -z "$README_VERSION" ]; then
|
||||
echo "⚠️ No VERSION found in README.md FILE INFORMATION block" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "**README version:** \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
DRIFT=0
|
||||
CHECKED=0
|
||||
|
||||
# Check all files with FILE INFORMATION blocks
|
||||
while IFS= read -r -d '' file; do
|
||||
FILE_VERSION=$(grep -oP '^\s*\*?\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' "$file" 2>/dev/null | head -1)
|
||||
[ -z "$FILE_VERSION" ] && continue
|
||||
CHECKED=$((CHECKED+1))
|
||||
if [ "$FILE_VERSION" != "$README_VERSION" ]; then
|
||||
echo " ⚠️ \`${file}\`: \`${FILE_VERSION}\` (expected \`${README_VERSION}\`)" >> $GITHUB_STEP_SUMMARY
|
||||
DRIFT=$((DRIFT+1))
|
||||
fi
|
||||
done < <(find . -maxdepth 4 -type f \( -name "*.php" -o -name "*.md" -o -name "*.yml" \) ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" -print0 2>/dev/null)
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "$DRIFT" -gt 0 ]; then
|
||||
echo "⚠️ **${DRIFT}** file(s) out of ${CHECKED} have version drift" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Run \`sync-version-on-merge\` workflow or update manually" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "✅ All ${CHECKED} file(s) match README version \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ── PROTECT CUSTOM WORKFLOWS ────────────────────────────────────────
|
||||
- name: Ensure custom workflow directory exists
|
||||
run: |
|
||||
echo "## 🔧 Custom Workflows" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ ! -d ".github/workflows/custom" ]; then
|
||||
mkdir -p .github/workflows/custom
|
||||
cat > .github/workflows/custom/README.md << 'CWEOF'
|
||||
# Custom Workflows
|
||||
|
||||
Place repo-specific workflows here. Files in this directory are:
|
||||
- **Never overwritten** by MokoStandards bulk sync
|
||||
- **Never deleted** by the repository-cleanup workflow
|
||||
- Safe for custom CI, notifications, or repo-specific automation
|
||||
|
||||
Synced workflows live in `.github/workflows/` (parent directory).
|
||||
CWEOF
|
||||
sed -i 's/^ //' .github/workflows/custom/README.md
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add .github/workflows/custom/
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "chore: create .github/workflows/custom/ for repo-specific workflows [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
echo "✅ Created \`.github/workflows/custom/\` directory" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
else
|
||||
CUSTOM_COUNT=$(find .github/workflows/custom -name "*.yml" -o -name "*.yaml" 2>/dev/null | wc -l)
|
||||
echo "✅ Custom workflow directory exists (${CUSTOM_COUNT} workflow(s))" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ── DELETE CLOSED ISSUES ──────────────────────────────────────────────
|
||||
- name: Delete old closed issues
|
||||
if: steps.tasks.outputs.delete_closed_issues == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
REPO="${{ github.repository }}"
|
||||
CUTOFF=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||
echo "## 🗑️ Closed Issue Cleanup" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Deleting issues closed before: ${CUTOFF}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
DELETED=0
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues?state=closed&since=1970-01-01T00:00:00Z&per_page=100&sort=updated&direction=asc" 2>/dev/null \
|
||||
| jq -r ".[] | select(.closed_at < \"${CUTOFF}\") | .number" 2>/dev/null | while read -r num; do
|
||||
# Lock and close with "not_planned" to mark as cleaned up
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/issues/${num}/lock" 2>/dev/null -X PUT -f lock_reason="resolved" --silent 2>/dev/null || true
|
||||
echo " Locked issue #${num}" >> $GITHUB_STEP_SUMMARY
|
||||
DELETED=$((DELETED+1))
|
||||
done
|
||||
|
||||
if [ "$DELETED" -eq 0 ] 2>/dev/null; then
|
||||
echo "✅ No old closed issues found" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "✅ Locked ${DELETED} old closed issue(s)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
echo "*Run by @${{ github.actor }} — trigger: ${{ github.event_name }}*" >> $GITHUB_STEP_SUMMARY
|
||||
2598
.github/workflows/standards-compliance.yml
vendored
Normal file
2598
.github/workflows/standards-compliance.yml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
133
.github/workflows/sync-version-on-merge.yml
vendored
Normal file
133
.github/workflows/sync-version-on-merge.yml
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# This file is part of a Moko Consulting project.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers
|
||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/sync-version-on-merge.yml in all governed repos.
|
||||
# README.md is the single source of truth for the repository version.
|
||||
|
||||
name: Sync Version from README
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Dry run (preview only, no commit)'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
sync-version:
|
||||
name: Propagate README version
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.GA_TOKEN || github.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up PHP
|
||||
run: |
|
||||
php -v && composer --version
|
||||
|
||||
- name: Setup MokoStandards tools
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
git clone --depth 1 --branch version/04 --quiet \
|
||||
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards.git" \
|
||||
/tmp/mokostandards
|
||||
cd /tmp/mokostandards
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
|
||||
- name: Auto-bump patch version
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.actor != 'gitea-actions[bot]' }}
|
||||
run: |
|
||||
if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^README\.md$'; then
|
||||
echo "README.md changed in this push — skipping auto-bump"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
RESULT=$(php /tmp/mokostandards/api/cli/version_bump.php --path .) || {
|
||||
echo "⚠️ Could not bump version — skipping"
|
||||
exit 0
|
||||
}
|
||||
echo "Auto-bumping patch: $RESULT"
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add README.md
|
||||
git commit -m "chore(version): auto-bump patch ${RESULT} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
|
||||
- name: Extract version from README.md
|
||||
id: readme_version
|
||||
run: |
|
||||
git pull --ff-only 2>/dev/null || true
|
||||
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "⚠️ No VERSION in README.md — skipping propagation"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
echo "✅ README.md version: $VERSION"
|
||||
|
||||
- name: Run version sync
|
||||
if: ${{ steps.readme_version.outputs.skip != 'true' && inputs.dry_run != true }}
|
||||
run: |
|
||||
php /tmp/mokostandards/api/maintenance/update_version_from_readme.php \
|
||||
--path . \
|
||||
--create-issue \
|
||||
--repo "${{ github.repository }}"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
|
||||
- name: Commit updated files
|
||||
if: ${{ steps.readme_version.outputs.skip != 'true' && inputs.dry_run != true }}
|
||||
run: |
|
||||
git pull --ff-only 2>/dev/null || true
|
||||
if git diff --quiet; then
|
||||
echo "ℹ️ No version changes needed — already up to date"
|
||||
exit 0
|
||||
fi
|
||||
VERSION="${{ steps.readme_version.outputs.version }}"
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add -A
|
||||
git commit -m "chore(version): sync badges and headers to ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
VERSION="${{ steps.readme_version.outputs.version }}"
|
||||
echo "## 📦 Version Sync — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Source:** \`README.md\` FILE INFORMATION block" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
346
.github/workflows/update-server.yml
vendored
Normal file
346
.github/workflows/update-server.yml
vendored
Normal file
@@ -0,0 +1,346 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Joomla
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /templates/workflows/joomla/update-server.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
|
||||
#
|
||||
# Writes updates.xml with multiple <update> entries:
|
||||
# - <tag>stable</tag> on push to main (from auto-release)
|
||||
# - <tag>rc</tag> on push to rc/**
|
||||
# - <tag>development</tag> on push to dev/**
|
||||
#
|
||||
# Joomla filters by user's "Minimum Stability" setting.
|
||||
|
||||
name: Update Joomla Update Server XML Feed
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Stability tag'
|
||||
required: true
|
||||
default: 'development'
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- rc
|
||||
- stable
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-xml:
|
||||
name: Update updates.xml
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.GA_TOKEN || github.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup MokoStandards tools
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
git clone --depth 1 --branch version/04 --quiet \
|
||||
"https://x-access-token:${GH_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoStandards.git" \
|
||||
/tmp/mokostandards 2>/dev/null || true
|
||||
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
|
||||
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Generate updates.xml entry
|
||||
run: |
|
||||
BRANCH="${{ github.ref_name }}"
|
||||
REPO="${{ github.repository }}"
|
||||
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||
|
||||
# Auto-bump patch on alpha/beta/rc branches (not dev — dev bumps manually)
|
||||
if [[ "$BRANCH" != dev/* ]]; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
BUMPED=$(php /tmp/mokostandards/api/cli/version_bump.php --path . 2>/dev/null || true)
|
||||
if [ -n "$BUMPED" ]; then
|
||||
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
|
||||
git add -A
|
||||
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
|
||||
git push 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Determine stability from branch or input
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
STABILITY="${{ inputs.stability }}"
|
||||
elif [[ "$BRANCH" == rc/* ]]; then
|
||||
STABILITY="rc"
|
||||
elif [[ "$BRANCH" == beta/* ]]; then
|
||||
STABILITY="beta"
|
||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||
STABILITY="alpha"
|
||||
elif [[ "$BRANCH" == dev/* ]]; then
|
||||
STABILITY="development"
|
||||
else
|
||||
STABILITY="stable"
|
||||
fi
|
||||
|
||||
# Parse manifest (portable — no grep -P)
|
||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No Joomla manifest found — skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract fields using sed (works on all runners)
|
||||
EXT_NAME=$(sed -n 's/.*<name>\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_CLIENT=$(sed -n 's/.*<extension[^>]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_VERSION=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1)
|
||||
TARGET_PLATFORM=$(sed -n 's/.*\(<targetplatform[^/]*\/>\).*/\1/p' "$MANIFEST" | head -1)
|
||||
PHP_MINIMUM=$(sed -n 's/.*<php_minimum>\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1)
|
||||
|
||||
# Fallbacks
|
||||
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
||||
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
||||
|
||||
# Templates and modules don't have <element> — derive from <name>
|
||||
if [ -z "$EXT_ELEMENT" ]; then
|
||||
EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
|
||||
fi
|
||||
|
||||
# Use manifest version if README version is empty
|
||||
[ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
|
||||
|
||||
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
||||
|
||||
CLIENT_TAG=""
|
||||
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
||||
[ -z "$CLIENT_TAG" ] && ([ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]) && CLIENT_TAG="<client>site</client>"
|
||||
|
||||
FOLDER_TAG=""
|
||||
[ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
|
||||
|
||||
PHP_TAG=""
|
||||
[ -n "$PHP_MINIMUM" ] && PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
||||
|
||||
# Version suffix for non-stable
|
||||
DISPLAY_VERSION="$VERSION"
|
||||
case "$STABILITY" in
|
||||
development) DISPLAY_VERSION="${VERSION}-dev" ;;
|
||||
alpha) DISPLAY_VERSION="${VERSION}-alpha" ;;
|
||||
beta) DISPLAY_VERSION="${VERSION}-beta" ;;
|
||||
rc) DISPLAY_VERSION="${VERSION}-rc" ;;
|
||||
esac
|
||||
|
||||
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
||||
|
||||
# Each stability level has its own release tag
|
||||
case "$STABILITY" in
|
||||
development) RELEASE_TAG="development" ;;
|
||||
alpha) RELEASE_TAG="alpha" ;;
|
||||
beta) RELEASE_TAG="beta" ;;
|
||||
rc) RELEASE_TAG="release-candidate" ;;
|
||||
*) RELEASE_TAG="v${MAJOR}" ;;
|
||||
esac
|
||||
|
||||
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
|
||||
DOWNLOAD_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
||||
INFO_URL="https://github.com/${REPO}"
|
||||
|
||||
# ── Build install packages (ZIP + tar.gz) ───────────────────
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
if [ -d "$SOURCE_DIR" ]; then
|
||||
EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*"
|
||||
TAR_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz"
|
||||
|
||||
cd "$SOURCE_DIR"
|
||||
zip -r "/tmp/${PACKAGE_NAME}" . -x $EXCLUDES
|
||||
cd ..
|
||||
tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \
|
||||
--exclude='.ftpignore' --exclude='sftp-config*' \
|
||||
--exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
|
||||
|
||||
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
||||
|
||||
# Ensure release exists
|
||||
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || \
|
||||
gh release create "$RELEASE_TAG" --title "${RELEASE_TAG} (${DISPLAY_VERSION})" --notes "${STABILITY} release" --prerelease --target main 2>/dev/null || true
|
||||
|
||||
# Upload both formats
|
||||
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || true
|
||||
gh release upload "$RELEASE_TAG" "/tmp/${TAR_NAME}" --clobber 2>/dev/null || true
|
||||
|
||||
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
SHA256=""
|
||||
fi
|
||||
|
||||
# ── Build the new entry ───────────────────────────────────────
|
||||
NEW_ENTRY=""
|
||||
NEW_ENTRY="${NEW_ENTRY} <update>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <description>${EXT_NAME} (${STABILITY})</description>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</type>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <version>${DISPLAY_VERSION}</version>\n"
|
||||
[ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n"
|
||||
[ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <tags>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <tag>${STABILITY}</tag>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} </tags>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
|
||||
TAR_URL="https://git.mokoconsulting.tech/${{ github.repository }}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz"
|
||||
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"tar.gz\">${TAR_URL}</downloadurl>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
|
||||
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>sha256:${SHA256}</sha256>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n"
|
||||
[ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} </update>"
|
||||
|
||||
# ── Write new entry to temp file ───────────────────────────────
|
||||
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
|
||||
|
||||
# ── Merge into updates.xml ─────────────────────────────────────
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' > updates.xml
|
||||
printf '%s\n' '<updates>' >> updates.xml
|
||||
cat /tmp/new_entry.xml >> updates.xml
|
||||
printf '\n%s\n' '</updates>' >> updates.xml
|
||||
else
|
||||
# Remove existing entry for this stability, insert new one
|
||||
printf 'import re\nstability = "%s"\n' "${STABILITY}" > /tmp/merge_xml.py
|
||||
printf 'with open("updates.xml") as f: content = f.read()\n' >> /tmp/merge_xml.py
|
||||
printf 'with open("/tmp/new_entry.xml") as f: new_entry = f.read()\n' >> /tmp/merge_xml.py
|
||||
printf 'pattern = r" <update>.*?<tag>" + re.escape(stability) + r"</tag>.*?</update>\\n?"\n' >> /tmp/merge_xml.py
|
||||
printf 'content = re.sub(pattern, "", content, flags=re.DOTALL)\n' >> /tmp/merge_xml.py
|
||||
printf 'content = content.replace("</updates>", new_entry + "\\n</updates>")\n' >> /tmp/merge_xml.py
|
||||
printf 'content = re.sub(r"\\n{3,}", "\\n\\n", content)\n' >> /tmp/merge_xml.py
|
||||
printf 'with open("updates.xml", "w") as f: f.write(content)\n' >> /tmp/merge_xml.py
|
||||
python3 /tmp/merge_xml.py 2>/dev/null || {
|
||||
# Fallback: rebuild keeping other stability entries
|
||||
{
|
||||
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
||||
printf '%s\n' '<updates>'
|
||||
for TAG in stable rc development; do
|
||||
[ "$TAG" = "${STABILITY}" ] && continue
|
||||
if grep -q "<tag>${TAG}</tag>" updates.xml 2>/dev/null; then
|
||||
sed -n "/<update>/,/<\/update>/{ /<tag>${TAG}<\/tag>/p; }" updates.xml
|
||||
fi
|
||||
done
|
||||
cat /tmp/new_entry.xml
|
||||
printf '\n%s\n' '</updates>'
|
||||
} > /tmp/updates_new.xml
|
||||
mv /tmp/updates_new.xml updates.xml
|
||||
}
|
||||
fi
|
||||
|
||||
# Commit
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add updates.xml
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: SFTP deploy to dev server
|
||||
if: contains(github.ref, 'dev/')
|
||||
env:
|
||||
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||
GH_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||
run: |
|
||||
# ── Permission check: admin or maintain role required ──────
|
||||
ACTOR="${{ github.actor }}"
|
||||
REPO="${{ github.repository }}"
|
||||
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}/permission" 2>/dev/null \
|
||||
2>/dev/null | jq -r '.permission' || \
|
||||
curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${{GITEA_URL:-https://git.mokoconsulting.tech}}/api/v1/repos/${{ github.repository }}/collaborators/${ACTOR}" 2>/dev/null \
|
||||
2>/dev/null | jq -r '.role' || echo "read")
|
||||
case "$PERMISSION" in
|
||||
admin|maintain|write) ;;
|
||||
*)
|
||||
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
|
||||
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||
|
||||
PORT="${DEV_PORT:-22}"
|
||||
REMOTE="${DEV_PATH%/}"
|
||||
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
|
||||
|
||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
|
||||
if [ -n "$DEV_KEY" ]; then
|
||||
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
|
||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||
else
|
||||
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||
fi
|
||||
|
||||
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null || true)
|
||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards/api/deploy/deploy-joomla.php" ]; then
|
||||
php /tmp/mokostandards/api/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
elif [ -f "/tmp/mokostandards/api/deploy/deploy-sftp.php" ]; then
|
||||
php /tmp/mokostandards/api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
fi
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY
|
||||
207
.gitignore
vendored
Normal file
207
.gitignore
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
# ============================================================
|
||||
# Local task tracking (not version controlled)
|
||||
# ============================================================
|
||||
TODO.md
|
||||
|
||||
# ============================================================
|
||||
# Environment and secrets
|
||||
# ============================================================
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
*.local.php
|
||||
*.secret.php
|
||||
configuration.php
|
||||
configuration.*.php
|
||||
configuration.local.php
|
||||
conf/conf.php
|
||||
conf/conf*.php
|
||||
secrets/
|
||||
*.secrets.*
|
||||
|
||||
# ============================================================
|
||||
# Logs, dumps and databases
|
||||
# ============================================================
|
||||
*.db
|
||||
*.db-journal
|
||||
*.dump
|
||||
*.log
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
|
||||
# ============================================================
|
||||
# OS / Editor / IDE cruft
|
||||
# ============================================================
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
$RECYCLE.BIN/
|
||||
System Volume Information/
|
||||
*.lnk
|
||||
Icon?
|
||||
.idea/
|
||||
.settings/
|
||||
.claude/
|
||||
.vscode/*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/settings.json.example
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
*.sublime*
|
||||
.project
|
||||
.buildpath
|
||||
.classpath
|
||||
*.bak
|
||||
*.swp
|
||||
*.swo
|
||||
*.tmp
|
||||
*.old
|
||||
*.orig
|
||||
|
||||
# ============================================================
|
||||
# Dev scripts and scratch
|
||||
# ============================================================
|
||||
TODO.md
|
||||
todo*
|
||||
*ffs*
|
||||
|
||||
# ============================================================
|
||||
# SFTP / sync tools
|
||||
# ============================================================
|
||||
sftp-config*.json
|
||||
sftp-config.json.template
|
||||
sftp-settings.json
|
||||
|
||||
# ============================================================
|
||||
# Sublime SFTP / FTP sync
|
||||
# ============================================================
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
*.sublime-settings
|
||||
.libsass.json
|
||||
*.ffs*
|
||||
|
||||
# ============================================================
|
||||
# Replit / cloud IDE
|
||||
# ============================================================
|
||||
.replit
|
||||
replit.md
|
||||
|
||||
# ============================================================
|
||||
# Archives / release artifacts
|
||||
# ============================================================
|
||||
*.7z
|
||||
*.rar
|
||||
*.tar
|
||||
*.tar.gz
|
||||
*.tgz
|
||||
*.zip
|
||||
artifacts/
|
||||
release/
|
||||
releases/
|
||||
|
||||
# ============================================================
|
||||
# Build outputs and site generators
|
||||
# ============================================================
|
||||
.mkdocs-build/
|
||||
.cache/
|
||||
.parcel-cache/
|
||||
build/
|
||||
dist/
|
||||
out/
|
||||
site/
|
||||
*.map
|
||||
*.css.map
|
||||
*.js.map
|
||||
*.tsbuildinfo
|
||||
|
||||
# ============================================================
|
||||
# CI / test artifacts
|
||||
# ============================================================
|
||||
.coverage
|
||||
.coverage.*
|
||||
coverage/
|
||||
coverage.xml
|
||||
htmlcov/
|
||||
junit.xml
|
||||
reports/
|
||||
test-results/
|
||||
tests/_output/
|
||||
.github/local/
|
||||
.github/workflows/*.log
|
||||
|
||||
# ============================================================
|
||||
# Node / JavaScript
|
||||
# ============================================================
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
.pnpm-store/
|
||||
.yarn/
|
||||
.npmrc
|
||||
.eslintcache
|
||||
package-lock.json
|
||||
|
||||
# ============================================================
|
||||
# PHP / Composer tooling
|
||||
# ============================================================
|
||||
composer.lock
|
||||
*.phar
|
||||
codeception.phar
|
||||
.phpunit.result.cache
|
||||
.php_cs.cache
|
||||
.php-cs-fixer.cache
|
||||
.phpstan.cache
|
||||
.phplint-cache
|
||||
phpmd-cache/
|
||||
.psalm/
|
||||
.rector/
|
||||
|
||||
# ============================================================
|
||||
# Python
|
||||
# ============================================================
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.pyc
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
.eggs/
|
||||
*.egg
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
MANIFEST
|
||||
develop-eggs/
|
||||
downloads/
|
||||
eggs/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
ENV/
|
||||
env/
|
||||
.venv/
|
||||
venv/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
.pyright/
|
||||
.tox/
|
||||
.nox/
|
||||
*.cover
|
||||
*.coverage
|
||||
hypothesis/
|
||||
|
||||
# Custom theme palettes (site-specific, not version controlled)
|
||||
# Note: src/templates/*.custom.css are STARTER templates (tracked)
|
||||
src/media/css/theme/*.custom.css
|
||||
src/media/css/theme/*.custom.min.css
|
||||
templates/*.custom.css
|
||||
update.xml
|
||||
.moko-standards
|
||||
630
CHANGELOG.md
Normal file
630
CHANGELOG.md
Normal file
@@ -0,0 +1,630 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoOnyx.Documentation
|
||||
PATH: ./CHANGELOG.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Changelog file documenting version history of MokoOnyx
|
||||
-->
|
||||
|
||||
# Changelog — MokoOnyx (VERSION: 03.09.03)
|
||||
|
||||
All notable changes to the MokoOnyx Joomla template are documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [03.10.00] - 2026-04-18 — Bridge Release (MokoOnyx → MokoOnyx)
|
||||
|
||||
### Important
|
||||
- **Template Rename** — MokoOnyx is being renamed to **MokoOnyx**. This bridge release automatically migrates your template settings, menu assignments, and files to the new name. MokoOnyx can be safely uninstalled after this update.
|
||||
|
||||
### Added
|
||||
- **Offline page redesign** — Full-viewport background from Joomla offline_image or header background, glass card overlay, centered logo with glow, login accordion, copyright footer
|
||||
- **CSS variable click-to-copy** — Text containing `--variable-name` patterns is wrapped in clickable chips that copy to clipboard with toast notification
|
||||
- **Brand-aside 3-column layout** — Flex columns like top-a with card style
|
||||
- **mod_stats table layout** — Converted from definition list to semantic table
|
||||
- **Favicon multi-format support** — Now handles PNG, JPEG, GIF, WebP, BMP (not just PNG)
|
||||
- **Theme variables** — `--theme-fab-bg`, `--theme-fab-color`, `--theme-fab-btn-bg`, `--theme-fab-border`, `--offline-card-bg`
|
||||
- **Footer CSS variables** — Added to CSS Variables reference tab
|
||||
- **Bridge migration script** — `helper/bridge.php` handles automatic MokoOnyx → MokoOnyx migration
|
||||
- **Dedicated release runner** — Release workflows run on isolated `release` label runner
|
||||
- **Runner fleet** — 3 CI + 1 release runner (12 concurrent jobs)
|
||||
|
||||
### Changed
|
||||
- **Gitea-primary CI/CD** — All workflows use Gitea API, GitHub is backup for stable/RC only
|
||||
- **Theme switcher** — Larger, bordered, theme-aware colors (off-white on dark, primary on light)
|
||||
- **Auto switch** — Red when off, green when on
|
||||
- **A11y toolbar** — Theme-aware colors for dark mode visibility
|
||||
- **Search button border** — Matches input border (`--input-border-color`)
|
||||
- **Offline message** — 0=hidden, 1=custom message, 2=system language string
|
||||
- **Light theme fonts** — Fixed trailing `)` syntax error, normalized quote style to match dark
|
||||
- **`--accent-color-secondary`** — Unified to `#6fb3ff` across both themes
|
||||
- **`--alert-color`** — Set to `#000` in light theme
|
||||
|
||||
### Removed
|
||||
- Brand showcase tab (redundant with theme preview)
|
||||
- Position selectors for a11y/theme FAB (forced to bottom-right)
|
||||
- Custom theme CSS from git tracking (site-specific, gitignored)
|
||||
|
||||
### Fixed
|
||||
- SHA-256 checksum format — Removed `sha256:` prefix (Joomla expects raw hex)
|
||||
- Favicon path resolution — Strips `#joomlaImage://` fragment, tries multiple path candidates
|
||||
- `REQUIRE_SIGNIN_VIEW` — Set to `false` for public release downloads
|
||||
- Release workflow — Uses Gitea API to update `updates.xml` on main (bypasses branch protection)
|
||||
- Language loading on offline page — `com_users` and core language files loaded explicitly
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased] - 2026-04-02
|
||||
|
||||
### Added
|
||||
|
||||
- **Favicon configuration** — New "Favicon" tab in template config; upload a PNG and all favicon sizes are auto-generated via PHP GD (ICO, Apple Touch Icon 180px, Android Chrome 192/512px, site.webmanifest)
|
||||
- **Module overrides** — 11 new `default.php` layout overrides for Joomla core modules: `mod_custom`, `mod_articles_latest`, `mod_articles_popular`, `mod_articles_news`, `mod_articles_category`, `mod_breadcrumbs`, `mod_footer`, `mod_login`, `mod_finder`, `mod_tags_popular`, `mod_tags_similar`, `mod_related_items`
|
||||
- **Module title support** — All module overrides respect `$module->showtitle`, `header_tag`, `header_class`, and `moduleclass_sfx` parameters
|
||||
- **Module CSS** — BEM-scoped styles for module titles, article lists, tag badges, search forms, login forms, breadcrumbs, and footer content
|
||||
- **Hero card variables** — Full variable-driven hero system: `--hero-card-bg`, `--hero-card-color`, `--hero-card-overlay`, `--hero-card-border-radius`, `--hero-card-padding-x/y`, `--hero-card-max-width`, plus `--hero-alt-card-*` for secondary variant
|
||||
- **Hero mobile breakpoint** — Photo background hidden on mobile (≤767.98px), hero card becomes full-bleed (100dvh, no border-radius)
|
||||
- **CSS fallback values** — 1365 `var()` calls in template.css now include inline fallback values
|
||||
- **Card border-radius** — `.card` now has `.25rem` fallback on `var(--card-border-radius)`
|
||||
- **Usage section in README** — Added missing "Usage" section required by MokoStandards
|
||||
|
||||
### Changed
|
||||
|
||||
- **Button backgrounds** — `--btn-bg: transparent` changed to `var(--body-bg)` in dark and light themes
|
||||
- **Offcanvas close button** — `.offcanvas-header .btn-close` now gets `background-color` from `--offcanvas-bg`
|
||||
- **Custom template sync** — Both `dark.custom.css` and `light.custom.css` now contain all variables from their standard counterparts (was missing 223 variables)
|
||||
- **Overlay layer** — Added `--hero-overlay-bg-position` and `--hero-overlay-bg-size` variables
|
||||
- **Legacy CSS cleanup** — Removed vendor prefixes (`-webkit-box`, `-ms-flexbox`) from `.overlay` rules, replaced with modern flexbox
|
||||
|
||||
### Removed
|
||||
|
||||
- **FILE INFORMATION headers** — Stripped DEFGROUP/INGROUP/PATH/VERSION/BRIEF metadata from all PHP, CSS, JS, INI, and HTML files (kept in XML and README per policy)
|
||||
- **Mobile overrides** — Deleted 26 `mobile.php` layout files and their empty parent directories
|
||||
- **Joomla-specific gitignore entries** — Removed ~700 lines of Joomla CMS core paths from `.gitignore` (not applicable to a template repository)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CI: composer install** — Workflow `standards-compliance.yml` now conditionally runs `composer install` only when `composer.json` exists
|
||||
- **CI: YAML syntax** — Fixed invalid YAML in `auto-update-sha.yml` caused by multiline commit message in run block
|
||||
|
||||
---
|
||||
|
||||
## [03.09.02] - 2026-03-26
|
||||
|
||||
### Added - Hero Variant System & Block Color System
|
||||
|
||||
#### Hero Variants
|
||||
- **`.hero#primary`** and **`.hero#secondary`** CSS variant system for visually distinct hero treatments
|
||||
- Shared `.hero` base class with `background-size: cover`, `border-radius: .5rem`, and `overflow: hidden`
|
||||
- Six new CSS variables (`--hero-primary-bg-color`, `--hero-primary-overlay`, `--hero-primary-color`, and secondary equivalents)
|
||||
- Light and dark mode defaults in custom palette templates
|
||||
|
||||
#### Block Color System
|
||||
- Automatic `:nth-child()` slot palette for `top-a`, `top-b`, `bottom-a`, `bottom-b` module positions
|
||||
- Four color slots (`--block-color-1` through `--block-color-4`) with matching text variables
|
||||
- Named per-module overrides: `#block-highlight`, `#block-cta`, `#block-alert`
|
||||
- ID specificity wins over `:nth-child()` — no `!important` needed
|
||||
|
||||
#### Files Modified
|
||||
- `src/media/css/template.css` — hero variant rules, block color `:nth-child()` rules, named override rules
|
||||
- `src/media/css/theme/light.standard.css` — hero and block color variables (light standard)
|
||||
- `src/media/css/theme/dark.standard.css` — hero and block color variables (dark standard)
|
||||
- `src/templates/light.custom.css` — hero and block color variables (light custom starter)
|
||||
- `src/templates/dark.custom.css` — hero and block color variables (dark custom starter)
|
||||
- `src/templateDetails.xml` — Theme Preview tab, hero/block note fields, scriptfile registration, version bump to 03.09.02
|
||||
- `src/language/en-GB/tpl_mokoonyx.ini` — language strings for new admin fields (British English)
|
||||
- `src/language/en-US/tpl_mokoonyx.ini` — language strings for new admin fields (American English)
|
||||
- `docs/CSS_VARIABLES.md` — full variable reference for both systems, sync script documentation
|
||||
- `CHANGELOG.md` — this entry
|
||||
|
||||
#### Files Added
|
||||
- `src/templates/theme-test.html` — Bootstrap-style test page with branded showcase, CSS variable swatches, hero demos, block color demos, and color test image
|
||||
- `src/script.php` — Joomla install/update lifecycle script (runs CSS variable sync on upgrade, checks PHP/Joomla minimum versions)
|
||||
- `src/sync_custom_vars.php` — CLI/library utility that detects missing CSS variables in user custom palettes and injects them
|
||||
- `src/templates/brand-showcase.html` — Interactive color system gradients with hover pixel sampler, Bootstrap component showcase
|
||||
|
||||
#### Variable Audit
|
||||
- All 20 hero/block variables confirmed present in all 4 theme files (light/dark standard + custom)
|
||||
- No duplicate variable declarations found across any theme file
|
||||
- `--gutter-x` references in template.css are self-scoped to grid containers (standard Bootstrap 5 behavior, not a `:root` variable)
|
||||
|
||||
---
|
||||
|
||||
## [03.08.03] - 2026-02-27
|
||||
|
||||
### Added - Main Menu Collapsible Dropdown Override
|
||||
|
||||
**New feature**: Added responsive "Main Menu" mod_menu override with Bootstrap 5 collapsible dropdown functionality.
|
||||
|
||||
#### What's New
|
||||
- **Main Menu module override** with full Bootstrap 5 responsive navbar
|
||||
- Collapsible hamburger menu for mobile devices
|
||||
- Multi-level dropdown support with hover on desktop, tap on mobile
|
||||
- WCAG 2.1 compliant touch targets (48px on mobile, 44px on desktop)
|
||||
- BEM naming convention: `.mod-menu-main__*`
|
||||
- **Appears as "Mainmenu" layout option** in Joomla admin module settings
|
||||
|
||||
#### Files Added
|
||||
- `src/templates/html/mod_menu/mainmenu.php` - Main layout with Bootstrap navbar
|
||||
- `src/templates/html/mod_menu/mainmenu_component.php` - Component menu items
|
||||
- `src/templates/html/mod_menu/mainmenu_heading.php` - Heading menu items
|
||||
- `src/templates/html/mod_menu/mainmenu_separator.php` - Separator menu items
|
||||
- `src/templates/html/mod_menu/mainmenu_url.php` - URL menu items
|
||||
- `src/templates/html/mod_menu/index.html` - Security file
|
||||
|
||||
#### Features
|
||||
- **Bootstrap 5 Navbar**: Uses Bootstrap's native navbar-nav structure
|
||||
- **Collapsible on Mobile**: Hamburger menu with smooth collapse animation
|
||||
- **Dropdown Menus**: Multi-level dropdown support with caret indicators
|
||||
- **Responsive Breakpoints**: Mobile-first design adapting at 768px and 992px
|
||||
- **Touch-Friendly**: 48px minimum touch targets on mobile
|
||||
- **Accessible**: ARIA labels and keyboard navigation support
|
||||
- **Active States**: Visual indicators for current and active menu items
|
||||
- **Alternative Layout**: Named `mainmenu.php` (not `default.php`) to appear as selectable layout option in Joomla admin
|
||||
|
||||
#### CSS Architecture
|
||||
- 200+ lines of responsive CSS in template.css
|
||||
- BEM naming: `.mod-menu-main`, `.mod-menu-main__list`, `.mod-menu-main__link`
|
||||
- CSS variables integration for colors and borders
|
||||
- Hover effects on desktop, tap effects on mobile
|
||||
- Smooth transitions and animations
|
||||
|
||||
#### Module Count Update
|
||||
- **Before**: 16 module overrides
|
||||
- **After**: 17 module overrides (added mod_menu "Main Menu")
|
||||
- **Component overrides**: Still 7 (unchanged)
|
||||
|
||||
### Removed - mod_search Override
|
||||
|
||||
**Cassiopeia approach**: Removed mod_search override to align with Cassiopeia template philosophy of not overriding standard Joomla modules.
|
||||
|
||||
#### Reason for Removal
|
||||
- mod_search is a standard Joomla core module
|
||||
- Following Cassiopeia template approach: use core layouts for standard modules
|
||||
- Prevents potential language loading issues
|
||||
- Ensures compatibility with future Joomla updates
|
||||
- Core mod_search already includes responsive design and accessibility features
|
||||
|
||||
#### Files Removed
|
||||
- `src/templates/html/mod_search/default.php` - Custom search module layout
|
||||
- `src/templates/html/mod_search/index.html` - Security file
|
||||
|
||||
#### Module Count Update (After Removal)
|
||||
- **Before**: 17 module overrides
|
||||
- **After**: 16 module overrides (removed mod_search)
|
||||
- **Component overrides**: Still 7 (unchanged)
|
||||
|
||||
### Removed - Documentation Cleanup
|
||||
|
||||
**Documentation policy**: Removed all markdown files from `src/templates/html/` directory. All documentation belongs in `docs/` folder only.
|
||||
|
||||
#### Files Removed (9 markdown files)
|
||||
- `src/templates/html/STANDARD_MODULES_README.md`
|
||||
- `src/templates/html/INDUSTRY_MODULES_README.md`
|
||||
- `src/templates/html/VIRTUEMART_MODULES_README.md`
|
||||
- `src/templates/html/mod_virtuemart_cart/README.md`
|
||||
- `src/templates/html/mod_virtuemart_category/README.md`
|
||||
- `src/templates/html/mod_virtuemart_currencies/README.md`
|
||||
- `src/templates/html/mod_virtuemart_manufacturer/README.md`
|
||||
- `src/templates/html/mod_virtuemart_product/README.md`
|
||||
- `src/templates/html/mod_search/README.md`
|
||||
|
||||
**Note**: All module override documentation is consolidated in `docs/MODULE_OVERRIDES.md`. The `src/templates/html/` directory now contains only PHP override files and `index.html` security files.
|
||||
|
||||
**Note**: Unlike the previously removed mod_menu override (v03.08.01), this new "Main Menu" override is properly structured based on Joomla core layouts and Bootstrap 5, ensuring language strings load correctly and menu functionality works as expected. The layout is named `mainmenu.php` (not `default.php`) to appear as an alternative layout option "Mainmenu" in the Joomla admin module dropdown selector, preserving Joomla's core default menu layout.
|
||||
|
||||
## [03.08.02] - 2026-02-27
|
||||
|
||||
### Removed - Fix Language Loading in All Module Overrides
|
||||
|
||||
**Critical fix**: Removed standard Joomla module overrides to fix language string loading issues. Following Cassiopeia template approach.
|
||||
|
||||
#### Problem
|
||||
- Default language strings not loading in module overrides (mod_breadcrumbs, mod_login, mod_articles_latest)
|
||||
- Language constants displayed instead of translated text (e.g., "MOD_LOGIN_VALUE_USERNAME" instead of "Username")
|
||||
- Custom overrides interfered with Joomla's module initialization and language loading process
|
||||
|
||||
#### Solution - Cassiopeia Approach
|
||||
- **Removed** standard Joomla module overrides:
|
||||
- `src/templates/html/mod_breadcrumbs/` (2 files)
|
||||
- `src/templates/html/mod_login/` (2 files)
|
||||
- `src/templates/html/mod_articles_latest/` (2 files)
|
||||
- Template now uses Joomla's core module layouts for standard modules
|
||||
- Language files load automatically via Joomla's module system
|
||||
- Custom styling can still be applied via CSS using module-specific classes
|
||||
- **Retained** third-party extension overrides where they add mobile-responsive value:
|
||||
- VirtueMart modules (5): mod_virtuemart_cart, _category, _currencies, _manufacturer, _product
|
||||
- Community Builder modules (2): mod_cblogin, mod_comprofilerOnline
|
||||
- Other extensions (9): mod_acymailing, mod_hikashop_cart, mod_k2_content, mod_kunena*, mod_osmembership, mod_search
|
||||
|
||||
#### Cassiopeia Template Philosophy
|
||||
- Cassiopeia (Joomla's default template) does NOT override standard module layouts
|
||||
- It relies on core Joomla module files and applies styling via CSS
|
||||
- Overrides are only created when structural changes are absolutely necessary
|
||||
- This ensures compatibility, automatic language loading, and easier maintenance
|
||||
|
||||
#### Module Count Update
|
||||
- **Before**: 19 module overrides
|
||||
- **After**: 16 module overrides
|
||||
- **Removed**: 3 standard Joomla modules (breadcrumbs, login, articles_latest)
|
||||
- **Component overrides**: Still 7 (unchanged)
|
||||
|
||||
#### Files Removed
|
||||
- `src/templates/html/mod_breadcrumbs/default.php`
|
||||
- `src/templates/html/mod_breadcrumbs/index.html`
|
||||
- `src/templates/html/mod_login/default.php`
|
||||
- `src/templates/html/mod_login/index.html`
|
||||
- `src/templates/html/mod_articles_latest/default.php`
|
||||
- `src/templates/html/mod_articles_latest/index.html`
|
||||
|
||||
**Note**: This follows Joomla best practices by using core layouts for standard modules. Styling is handled via CSS. Third-party extension overrides remain for mobile responsiveness.
|
||||
|
||||
## [03.08.01] - 2026-02-27
|
||||
|
||||
### Removed - Fix Breaking Overrides
|
||||
|
||||
**Critical fix**: Removed mod_menu override that was causing menu links to break and language strings not to load.
|
||||
|
||||
#### Problem
|
||||
- mod_menu override files (default.php, default_component.php, default_url.php) were attempting to load menu-specific layouts that don't exist in the template
|
||||
- This broke Joomla's core menu rendering system
|
||||
- Menu links were not functional
|
||||
- Language strings were not loading properly in menus
|
||||
|
||||
#### Solution
|
||||
- **Removed** entire `src/templates/html/mod_menu/` directory (4 files)
|
||||
- Template now uses Joomla's default menu rendering
|
||||
- Custom styling can still be applied via CSS using `.mod-menu` class
|
||||
- All menu functionality restored to standard Joomla behavior
|
||||
|
||||
#### Documentation Updates
|
||||
- Updated MODULE_OVERRIDES.md: Changed count from 20 to 19 module overrides, removed mod_menu section, added note about removal
|
||||
- Updated STANDARD_MODULES_README.md: Removed mod_menu documentation, renumbered remaining modules, updated file structure
|
||||
- Updated testing checklists to remove mod_menu references
|
||||
- **Added clarification**: MokoOnyx is a standalone template extension (not a package)
|
||||
- Updated updates.xml to version 03.08.01
|
||||
|
||||
#### Files Removed
|
||||
- `src/templates/html/mod_menu/default.php`
|
||||
- `src/templates/html/mod_menu/default_component.php`
|
||||
- `src/templates/html/mod_menu/default_url.php`
|
||||
- `src/templates/html/mod_menu/index.html`
|
||||
|
||||
**Note**: This is a patch release that removes problematic overrides to restore core functionality. Menu styling via CSS remains intact. MokoOnyx remains a standalone Joomla template extension (type="template"), not bundled as a package.
|
||||
|
||||
## [03.08.00] - 2026-02-22
|
||||
|
||||
### Added - Community Builder Component Overrides
|
||||
|
||||
Minor version bump adding **4 Community Builder component view overrides** to complement the existing CB module overrides (mod_cblogin, mod_comprofilerOnline).
|
||||
|
||||
#### Community Builder Components (4 views)
|
||||
- **com_comprofiler/userprofile**: User profile display with avatar, tabs, and custom fields in responsive layout
|
||||
- **com_comprofiler/userslist**: User directory with search functionality and responsive grid (1-3 columns)
|
||||
- **com_comprofiler/registers**: User registration form with multi-step fieldsets, validation, captcha support
|
||||
- **com_comprofiler/login**: Login page with remember me checkbox, registration and password recovery links
|
||||
|
||||
#### CSS Architecture (600+ lines)
|
||||
- Mobile-first responsive design with Bootstrap breakpoints (576px, 768px, 992px)
|
||||
- BEM naming convention (`.cb-profile__`, `.cb-userslist__`, `.cb-register__`, `.cb-login__`)
|
||||
- Integrated with template CSS variables for consistent theming
|
||||
- 48px touch targets on mobile, 44px on desktop (WCAG 2.1 Level AA)
|
||||
- 16px input font size on mobile to prevent iOS zoom
|
||||
- Responsive grids adapting from 1 column (mobile) to 2-3 columns (desktop)
|
||||
|
||||
#### Accessibility Features
|
||||
- Full ARIA labels and descriptions for screen readers
|
||||
- Semantic HTML5 structure with proper landmarks
|
||||
- Keyboard navigation support throughout
|
||||
- Required field indicators with visually-hidden labels
|
||||
- Focus states with visible outlines
|
||||
|
||||
#### Security Best Practices
|
||||
- Proper output escaping with htmlspecialchars() and ENT_QUOTES
|
||||
- _JEXEC security checks in all PHP files
|
||||
- index.html protection files in all directories (6 files)
|
||||
- CSRF token support in forms
|
||||
- Input validation and error display
|
||||
|
||||
### Technical Details
|
||||
- **Files Added**: 11 (4 component view files + 6 index.html + 1 root index.html)
|
||||
- **CSS Lines Added**: 600+ lines of responsive styles
|
||||
- **PHP Validation**: All files pass syntax validation
|
||||
- **Component Views**: userprofile, userslist, registers, login
|
||||
- **Documentation**: Ready for MODULE_OVERRIDES.md update
|
||||
|
||||
## [03.07.00] - 2026-02-22
|
||||
|
||||
### Added - Mobile-Responsive Module & Component Overrides
|
||||
|
||||
This major release introduces **20 mobile-responsive module overrides** and **3 component overrides** designed to enhance the mobile user experience across standard Joomla, VirtueMart, Community Builder, and popular third-party extensions.
|
||||
|
||||
#### Search Module
|
||||
- **mod_search**: Mobile-responsive search with multiple button positions (left, right, top, bottom), 48px touch targets, 16px input font to prevent iOS zoom
|
||||
|
||||
#### VirtueMart E-Commerce Modules (5 modules)
|
||||
- **mod_virtuemart_cart**: Shopping cart with responsive product cards, remove buttons, price display
|
||||
- **mod_virtuemart_product**: Product showcase with responsive grid (1-4 columns), hover effects, ratings
|
||||
- **mod_virtuemart_currencies**: Currency selector dropdown with accessible styling
|
||||
- **mod_virtuemart_category**: Category navigation with hierarchical display, product counts
|
||||
- **mod_virtuemart_manufacturer**: Manufacturer/brand display with responsive grid (2-4 columns)
|
||||
- **VIRTUEMART_MODULES_README.md**: Comprehensive master documentation for all VirtueMart overrides
|
||||
|
||||
#### Standard Joomla & Community Builder Modules (6 modules)
|
||||
- **mod_menu**: Main navigation with multiple layout files (default, component, URL), responsive horizontal/vertical layouts
|
||||
- **mod_breadcrumbs**: Breadcrumb navigation with Schema.org markup for SEO
|
||||
- **mod_login**: User login/logout form with 2FA support, remember me checkbox
|
||||
- **mod_articles_latest**: Latest articles with responsive cards, metadata, featured badges
|
||||
- **mod_cblogin**: Community Builder login with avatar display, profile links
|
||||
- **mod_comprofilerOnline**: CB online users with avatar grid, online status indicators
|
||||
- **STANDARD_MODULES_README.md**: Comprehensive master documentation for standard module overrides
|
||||
|
||||
#### Industry Extension Modules (8 modules + 2 components)
|
||||
- **mod_k2_content**: K2 content display with responsive grid (1-3 columns), featured images, metadata
|
||||
- **mod_acymailing**: Newsletter subscription form with validation, GDPR compliance
|
||||
- **mod_hikashop_cart**: HikaShop shopping cart with product list, quantity adjustment
|
||||
- **mod_kunenalatest**: Kunena forum latest posts with excerpts, avatars, reply counts
|
||||
- **mod_kunenalogin**: Kunena forum login with user avatar, statistics, quick login
|
||||
- **mod_kunenasearch**: Kunena forum search with multiple button positions
|
||||
- **mod_kunenastats**: Kunena forum statistics with visual cards, member/topic counts
|
||||
- **mod_osmembership**: OS Membership Pro plans with pricing cards, feature lists, badges
|
||||
- **com_kunena/category**: Kunena forum category list component view
|
||||
- **com_osmembership/plans**: OS Membership Pro responsive pricing table component view
|
||||
- **INDUSTRY_MODULES_README.md**: Comprehensive master documentation for industry extensions
|
||||
|
||||
#### CSS & Styling
|
||||
- Added **2,000+ lines** of mobile-responsive CSS to `src/media/css/template.css`
|
||||
- Four dedicated CSS sections for organized styling:
|
||||
- MOD_SEARCH MOBILE RESPONSIVE STYLES
|
||||
- VIRTUEMART MODULE MOBILE RESPONSIVE STYLES
|
||||
- STANDARD JOOMLA & COMMUNITY BUILDER MODULE STYLES
|
||||
- INDUSTRY EXTENSION MODULE STYLES
|
||||
- ADDITIONAL KUNENA & MEMBERSHIP PRO MODULE STYLES
|
||||
- BEM naming convention for all CSS classes (`.mod-search__button`, `.mod-vm-product__grid`, etc.)
|
||||
- Integration with existing template CSS variables for seamless theming
|
||||
- Responsive grids with Bootstrap-aligned breakpoints (sm, md, lg, xl, xxl)
|
||||
|
||||
#### Documentation
|
||||
- **docs/MODULE_OVERRIDES.md**: Comprehensive guide covering all 23 overrides
|
||||
- Feature descriptions and specifications
|
||||
- CSS architecture and customization guide
|
||||
- Accessibility features documentation
|
||||
- Troubleshooting guide
|
||||
- Best practices and usage examples
|
||||
- Individual README.md files for VirtueMart module groups (5 modules)
|
||||
- Master README files for each category (VirtueMart, Standard, Industry)
|
||||
- Security index.html files in all override directories (23 files)
|
||||
|
||||
### Key Features Across All Overrides
|
||||
|
||||
#### Mobile-First Responsive Design
|
||||
- Touch targets: 48px on mobile, 44px on desktop (WCAG 2.1 compliant)
|
||||
- 16px minimum input font size on mobile (prevents iOS zoom)
|
||||
- Responsive layouts: 1-4 columns based on screen size
|
||||
- Mobile-first CSS with progressive enhancement
|
||||
- Bootstrap-aligned breakpoints: 576px, 768px, 992px, 1200px, 1400px
|
||||
|
||||
#### Accessibility
|
||||
- Full ARIA labels and descriptions on all interactive elements
|
||||
- Keyboard navigation support throughout
|
||||
- Screen reader compatible with semantic HTML5
|
||||
- WCAG 2.1 Level AA compliance
|
||||
- Proper heading hierarchy and focus management
|
||||
- Alternative text for images and icons
|
||||
|
||||
#### Security
|
||||
- Proper output escaping with Joomla escapeHtml()
|
||||
- _JEXEC security checks in all PHP files
|
||||
- index.html protection files in all directories
|
||||
- Input validation where applicable
|
||||
- CSRF token support in forms
|
||||
|
||||
#### Maintainability
|
||||
- BEM naming convention for CSS classes
|
||||
- Consistent code structure across all overrides
|
||||
- Comprehensive inline documentation
|
||||
- Modular, reusable components
|
||||
- Integration with template CSS variables
|
||||
|
||||
### Changed
|
||||
- **Version**: Updated to 03.07.00 across all files
|
||||
|
||||
### Technical Details
|
||||
- **Total Files**: 66 new files created
|
||||
- 42 PHP override files
|
||||
- 23 index.html security files
|
||||
- 1 comprehensive MODULE_OVERRIDES.md documentation
|
||||
- **CSS Added**: 2,000+ lines of responsive styles
|
||||
- **Documentation**: 15,000+ words across all README files
|
||||
|
||||
### Migration Notes
|
||||
- All overrides are opt-in and non-breaking
|
||||
- Existing sites will continue to work without changes
|
||||
- Overrides automatically apply when modules are used
|
||||
- No database changes or migration required
|
||||
- Custom overrides can coexist with template overrides
|
||||
|
||||
### Testing
|
||||
- All PHP syntax validated
|
||||
- Code review completed (all issues resolved)
|
||||
- CodeQL security scan passed
|
||||
- Responsive design tested across breakpoints
|
||||
- Accessibility validated with ARIA compliance
|
||||
|
||||
---
|
||||
|
||||
## [03.06.03] - 2026-01-30
|
||||
|
||||
### Added
|
||||
- **Templates Directory**: Created `/templates/` directory with ready-to-use color palette templates
|
||||
- `colors_custom_light.css` - Comprehensive light mode color template with all available variables
|
||||
- `colors_custom_dark.css` - Comprehensive dark mode color template with all available variables
|
||||
- **CSS Variables Documentation**: Added complete CSS variables reference guide (`docs/CSS_VARIABLES.md`)
|
||||
- Complete list of all customizable CSS variables
|
||||
- Organized by category (colors, typography, borders, etc.)
|
||||
- Usage examples and tips for customization
|
||||
- Light and dark mode variable differences documented
|
||||
|
||||
### Changed
|
||||
- **README**: Updated title to "README - MokoOnyx (VERSION: 03.09.03)"
|
||||
- **README**: Fixed custom color variables instructions with correct file paths
|
||||
- **README**: Updated example CSS variables to use actual template variable names (e.g., `--color-link` instead of `--cassiopeia-color-link`)
|
||||
- **README**: Added note that custom color files are excluded from version control via `.gitignore`
|
||||
- **README**: Enhanced Custom Color Palettes section with step-by-step instructions
|
||||
- **README**: Added link to CSS Variables documentation for complete reference
|
||||
- **TOC CSS**: Updated bootstrap-toc.css to use template color variables for proper theme integration
|
||||
- **Version**: Updated version to 03.06.03 across all files
|
||||
|
||||
### Documentation
|
||||
- **docs/README.md**: Added CSS Variables Reference to developer documentation section
|
||||
- **docs/README.md**: Updated project structure to include `/templates/` directory
|
||||
- **docs/README.md**: Updated version to 03.06.03
|
||||
- Clarified that `colors_custom.css` files are gitignored to prevent fork-specific customizations from being committed
|
||||
|
||||
## [03.06.02] - 2026-01-30
|
||||
|
||||
### Major Rebrand
|
||||
This release includes a complete rebrand from "Moko-Cassiopeia" (hyphenated) to "MokoOnyx" (camelCase).
|
||||
|
||||
### Changed
|
||||
- **Naming Convention**: Changed template identifier from `moko-cassiopeia` to `mokoonyx` across all files
|
||||
- **Display Name**: Updated from "Moko-Cassiopeia" to "MokoOnyx" in all documentation and language files
|
||||
- **Language Constants**: Renamed all language keys from `TPL_MOKO-CASSIOPEIA_*` to `TPL_MOKOONYX_*`
|
||||
- **Language Files**: Renamed from `tpl_moko-cassiopeia.*` to `tpl_mokoonyx.*` (4 files)
|
||||
- **Media Paths**: Updated from `media/templates/site/moko-cassiopeia/` to `media/templates/site/mokoonyx/`
|
||||
- **Repository URLs**: Updated all references to use `MokoOnyx` casing
|
||||
- **Template Element**: Changed Joomla extension element name from `moko-cassiopeia` to `mokoonyx`
|
||||
- **Documentation**: Updated all markdown files, XML manifests, and code comments
|
||||
|
||||
### Removed
|
||||
- **Default Assets**: Removed `logo.svg` and `favicon.ico` to allow clean installations
|
||||
- **Template Overrides**: Removed all template override files (48 files, ~4,500 lines)
|
||||
- Removed `src/templates/html/` folder entirely
|
||||
- Removed overrides for: com_content, com_contact, com_engage, mod_menu, mod_custom, mod_gabble, layouts/chromes
|
||||
- Template now inherits all rendering from Joomla Cassiopeia defaults
|
||||
- Updated `templateDetails.xml` to remove html folder reference
|
||||
|
||||
### Breaking Changes
|
||||
⚠️ **Important**: This release contains breaking changes:
|
||||
- Existing installations will see template name change in Joomla admin
|
||||
- Custom code referencing old language constants (`TPL_MOKO-CASSIOPEIA_*`) will need updates
|
||||
- Custom code referencing old media paths will need updates
|
||||
- Sites relying on custom template overrides will revert to Cassiopeia defaults
|
||||
- Extension element name changed (may require reinstallation in some cases)
|
||||
|
||||
### Migration Notes
|
||||
- Backup your site before upgrading
|
||||
- Review any custom code for references to old naming convention
|
||||
- Test thoroughly after upgrade, especially if using custom overrides
|
||||
|
||||
## [03.06.00] - 2026-01-28
|
||||
|
||||
### Changed
|
||||
- Updated version to 03.06.00 across all files
|
||||
- Standardized version numbering format
|
||||
|
||||
## [03.05.01] - 2026-01-09
|
||||
|
||||
### Added
|
||||
- Added `dependency-review.yml` workflow for dependency vulnerability scanning
|
||||
- Added `standards-compliance.yml` workflow for MokoStandards validation
|
||||
- Added `.github/dependabot.yml` configuration for automated security updates
|
||||
- Added `docs/README.md` as documentation index
|
||||
|
||||
### Changed
|
||||
- Removed custom `codeql-analysis.yml` workflow (repository uses GitHub's default CodeQL setup)
|
||||
- Enforced repository compliance with MokoStandards requirements
|
||||
- Improved security posture with automated scanning and dependency management
|
||||
|
||||
## [03.05.00] - 2026-01-04
|
||||
|
||||
### Added
|
||||
- Created `.github/workflows` directory structure
|
||||
|
||||
### Changed
|
||||
- Replaced `./CODE_OF_CONDUCT.md` from `MokoStandards`
|
||||
- Replaced `./CONTRIBUTING.md` from `MokoStandards`
|
||||
- TODO split to own file
|
||||
|
||||
## [03.01.00] - 2025-12-16
|
||||
|
||||
### Added
|
||||
- Created `.github/workflows/` directory for GitHub Actions
|
||||
|
||||
## [03.00.00] - 2025-12-09
|
||||
|
||||
### Changed
|
||||
- Copyright Headers updated to MokoCodingDefaults standards
|
||||
- Fixed `./templates/mokoonyx/index.php` color style injection
|
||||
- Upgraded Font Awesome 6 to Font Awesome 7 Free
|
||||
- Added Font Awesome 7 Free style fallback
|
||||
|
||||
### Removed
|
||||
- Removed `./CODE_OF_CONDUCT.md` (replaced with MokoStandards version)
|
||||
- Removed `./CONTRIBUTING.md` (replaced with MokoStandards version)
|
||||
|
||||
## [02.01.05] - 2025-09-04
|
||||
|
||||
### Changed
|
||||
- Repaired template.css and colors_standard.css
|
||||
|
||||
### Removed
|
||||
- Removed vmbasic.css
|
||||
|
||||
## [02.00.00] - 2025-08-30
|
||||
|
||||
### Added - Dark Mode Toggle
|
||||
- Frontend toggle switch included in template
|
||||
- JavaScript handles switching between light/dark modes
|
||||
- Dark mode CSS rules applied across template styles
|
||||
- Automatic persistence of user choice (via localStorage)
|
||||
- Admins can override default mode in template settings
|
||||
|
||||
### Added - Header Parameters Update
|
||||
- Added logo parameter support in template settings
|
||||
- Updated metadata & copyright header
|
||||
|
||||
### Added - Expanded TOC (Table of Contents)
|
||||
- Automatic TOC injection when enabled
|
||||
- User selects placement via article > options > layout (`toc-left` or `toc-right`)
|
||||
|
||||
### Changed
|
||||
- Cleaned up `index.php` by removing skip-to-content duplicate calls
|
||||
- Consolidated JavaScript asset loading (ensuring dark-mode script is loaded correctly from external JS file)
|
||||
- Streamlined CSS for toggle switch, ensuring it inherits Bootstrap/Cassiopeia defaults
|
||||
- General accessibility refinements in typography and color contrast
|
||||
- Fixed missing logo param in header output
|
||||
- Corrected stylesheet inconsistencies between Bootstrap 5 helpers and template overrides
|
||||
- Patched redundant calls in script includes
|
||||
|
||||
## [01.00.00] - 2025-01-01
|
||||
|
||||
### Added - Initial Public Release
|
||||
- Font Awesome 6 integration (later upgraded to FA7)
|
||||
- Bootstrap 5 helpers (grid, utility classes)
|
||||
- Automatic Table of Contents (TOC) utility
|
||||
- Moko Expansions: Google Tag Manager / GA4 hooks
|
||||
- Built on top of Joomla's default Cassiopeia template
|
||||
- Minimal core template overrides for maximum upgrade compatibility
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
- **Full Roadmap**: [MokoOnyx Roadmap](https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap)
|
||||
- **Repository**: [GitHub](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
- **Issue Tracker**: [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||
|
||||
## Version Format
|
||||
|
||||
This project uses semantic versioning: `MAJOR.MINOR.PATCH`
|
||||
- **MAJOR**: Incompatible API changes or major overhauls
|
||||
- **MINOR**: New features, backwards-compatible
|
||||
- **PATCH**: Bug fixes, backwards-compatible
|
||||
98
CODE_OF_CONDUCT.md
Normal file
98
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,98 @@
|
||||
<!--
|
||||
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template
|
||||
INGROUP: MokoOnyx.Governance
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: CODE_OF_CONDUCT.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Contributor code of conduct for the MokoOnyx project.
|
||||
PATH: /CODE_OF_CONDUCT.md
|
||||
NOTE: This document defines behavioral expectations and enforcement processes.
|
||||
-->
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This Code of Conduct establishes expectations for behavior within the MokoOnyx project community. The objective is to maintain a professional, inclusive, and respectful environment aligned with open source governance best practices.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies to all project spaces, including:
|
||||
|
||||
* GitHub repositories, issues, pull requests, discussions, and security advisories.
|
||||
* Project documentation, workflows, and release processes.
|
||||
* Any communication channels officially associated with the project.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Participants are expected to:
|
||||
|
||||
* Communicate professionally and respectfully.
|
||||
* Provide constructive feedback focused on technical merit and project objectives.
|
||||
* Respect differing viewpoints, experience levels, and backgrounds.
|
||||
* Follow documented contribution, security, and governance policies.
|
||||
|
||||
Unacceptable behavior includes:
|
||||
|
||||
* Harassment, discrimination, or exclusionary conduct.
|
||||
* Personal attacks, insults, or inflammatory comments.
|
||||
* Publishing private information without consent.
|
||||
* Disruptive behavior that materially interferes with project operations.
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Project maintainers are responsible for:
|
||||
|
||||
* Clarifying standards when questions arise.
|
||||
* Taking appropriate and proportionate corrective action when violations occur.
|
||||
* Maintaining confidentiality to the extent practical during investigations.
|
||||
|
||||
## Reporting
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported through:
|
||||
|
||||
* Email: `hello@mokoconsulting.tech` with subject `CODE OF CONDUCT: MokoOnyx`.
|
||||
|
||||
Reports should include relevant context, links, screenshots, or other supporting information.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Corrective actions may include, but are not limited to:
|
||||
|
||||
* Private warning or request for corrective action.
|
||||
* Temporary or permanent restriction from project participation.
|
||||
* Removal of content that violates this Code of Conduct.
|
||||
|
||||
Decisions are made based on impact, severity, and pattern of behavior.
|
||||
|
||||
## No Retaliation
|
||||
|
||||
Retaliation against individuals who report concerns in good faith is not tolerated. Any retaliatory behavior will be treated as a separate violation.
|
||||
|
||||
## Jurisdiction
|
||||
|
||||
This project is managed from Tennessee, USA. This statement is informational and does not constitute legal advice.
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* **Document:** CODE_OF_CONDUCT.md
|
||||
* **Repository:** [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* **Path:** /CODE_OF_CONDUCT.md
|
||||
* **Owner:** Moko Consulting
|
||||
* **Version:** 03.06.00
|
||||
* **Status:** Active
|
||||
* **Effective Date:** 2025-12-18
|
||||
* **Last Reviewed:** 2025-12-18
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ----------------------------------------------------------------------------- | --------------- |
|
||||
| 2025-12-18 | Initial publication of contributor conduct standards and enforcement process. | Moko Consulting |
|
||||
145
CONTRIBUTING.md
Normal file
145
CONTRIBUTING.md
Normal file
@@ -0,0 +1,145 @@
|
||||
<!--
|
||||
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template
|
||||
INGROUP: MokoOnyx.Governance
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: CONTRIBUTING.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Contribution guidelines for the MokoOnyx project.
|
||||
PATH: /CONTRIBUTING.md
|
||||
NOTE: This document defines contribution workflow, standards, and governance alignment.
|
||||
-->
|
||||
|
||||
## Contributing
|
||||
|
||||
This document defines how to contribute to the MokoOnyx project. The goal is to ensure changes are reviewable, auditable, and aligned with project governance and release processes.
|
||||
|
||||
## Scope
|
||||
|
||||
These guidelines apply to all contributions, including:
|
||||
|
||||
* Source code changes
|
||||
* Documentation updates
|
||||
* Bug reports and enhancement proposals
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Contributors are expected to:
|
||||
|
||||
* Have a working understanding of Joomla template structure.
|
||||
* Be familiar with Git and GitHub pull request workflows.
|
||||
* Review repository governance documents prior to submitting changes.
|
||||
* Set up the development environment using the provided tools.
|
||||
|
||||
### Quick Setup
|
||||
|
||||
For first-time contributors:
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx.git
|
||||
cd MokoOnyx
|
||||
```
|
||||
|
||||
See [docs/QUICK_START.md](./docs/QUICK_START.md) for detailed setup instructions.
|
||||
|
||||
## Development Tools
|
||||
|
||||
The repository provides several tools to streamline development:
|
||||
|
||||
* **Pre-commit Hooks**: Automatic local validation before commits
|
||||
|
||||
## Contribution Workflow
|
||||
|
||||
1. Fork the repository.
|
||||
2. Create a branch from the active development branch.
|
||||
3. Make focused, minimal changes that address a single concern.
|
||||
4. Submit a pull request with a clear description of intent and impact.
|
||||
|
||||
Direct commits to protected branches are not permitted.
|
||||
|
||||
## Branching and Versioning
|
||||
|
||||
* Development work occurs on designated development branches.
|
||||
* Releases are produced from versioned branches following repository standards.
|
||||
* Contributors should not bump version numbers unless explicitly requested.
|
||||
|
||||
## Coding and Formatting Standards
|
||||
|
||||
All contributions must:
|
||||
|
||||
* Follow Joomla coding standards where applicable.
|
||||
* Conform to Moko Consulting repository standards for headers, metadata, and file structure.
|
||||
* Avoid introducing tabs, inconsistent path separators, or non portable assumptions.
|
||||
|
||||
Automated checks may reject changes that do not meet these requirements.
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
Documentation changes must:
|
||||
|
||||
* Include required metadata and revision history sections.
|
||||
* Avoid embedding version numbers in revision history tables.
|
||||
* Preserve existing structure unless a structural change is explicitly proposed.
|
||||
|
||||
## Commit Messages
|
||||
|
||||
Commit messages should:
|
||||
|
||||
* Be concise and descriptive.
|
||||
* Focus on what changed and why.
|
||||
* Avoid referencing internal issue trackers unless required.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Bug reports and enhancement requests should be filed as GitHub issues and include:
|
||||
|
||||
* Clear reproduction steps or use cases.
|
||||
* Expected versus actual behavior.
|
||||
* Relevant environment details.
|
||||
|
||||
Security related issues must follow the process defined in SECURITY.md and must not be reported publicly.
|
||||
|
||||
## Review Process
|
||||
|
||||
All pull requests are subject to review. Review criteria include:
|
||||
|
||||
* Technical correctness
|
||||
* Alignment with project goals
|
||||
* Maintainability and clarity
|
||||
* Risk introduced to release and update processes
|
||||
|
||||
Maintainers may request changes prior to approval.
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under GPL-3.0-or-later, consistent with the rest of the project.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Participation in this project is governed by the Code of Conduct. Unacceptable behavior may result in contribution restrictions.
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* **Document:** CONTRIBUTING.md
|
||||
* **Repository:** [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* **Path:** /CONTRIBUTING.md
|
||||
* **Owner:** Moko Consulting
|
||||
* **Version:** 03.06.00
|
||||
* **Status:** Active
|
||||
* **Effective Date:** 2025-12-18
|
||||
* **Last Reviewed:** 2025-12-18
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ------------------------------------------------------------------------- | --------------- |
|
||||
| 2025-12-18 | Initial publication of contribution guidelines and workflow expectations. | Moko Consulting |
|
||||
115
GOVERNANCE.md
Normal file
115
GOVERNANCE.md
Normal file
@@ -0,0 +1,115 @@
|
||||
<!--
|
||||
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template
|
||||
INGROUP: MokoOnyx.Governance
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: GOVERNANCE.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Project governance model, roles, and decision processes for MokoOnyx.
|
||||
PATH: /GOVERNANCE.md
|
||||
NOTE: This document defines authority, decision making, and escalation paths.
|
||||
-->
|
||||
|
||||
## Governance Overview
|
||||
|
||||
This document defines the governance framework for the MokoOnyx project. The objective is to ensure clear ownership, predictable decision making, and accountable stewardship across development, releases, and community interaction.
|
||||
|
||||
## Project Ownership
|
||||
|
||||
MokoOnyx is owned and maintained by **Moko Consulting**. Final authority for project direction, releases, and policy enforcement resides with the project owner.
|
||||
|
||||
## Roles and Responsibilities
|
||||
|
||||
### Maintainers
|
||||
|
||||
Maintainers are responsible for:
|
||||
|
||||
* Setting technical direction and release priorities.
|
||||
* Reviewing and approving pull requests.
|
||||
* Managing releases and distribution artifacts.
|
||||
* Enforcing repository policies, including security and conduct requirements.
|
||||
|
||||
### Contributors
|
||||
|
||||
Contributors may:
|
||||
|
||||
* Submit pull requests and issues.
|
||||
* Propose enhancements and report defects.
|
||||
* Participate in technical discussions.
|
||||
|
||||
Contributors do not have merge authority unless explicitly granted.
|
||||
|
||||
## Decision Making
|
||||
|
||||
Decisions are made using a maintainers led model:
|
||||
|
||||
* Routine changes are approved through pull request review.
|
||||
* Material changes affecting architecture, branding, licensing, or release processes require maintainer consensus.
|
||||
* The project owner retains final decision authority if consensus cannot be reached.
|
||||
|
||||
## Change Management
|
||||
|
||||
Significant changes should:
|
||||
|
||||
* Be documented through issues or pull requests with clear rationale.
|
||||
* Consider backward compatibility and upgrade impact.
|
||||
* Include documentation updates when behavior or usage changes.
|
||||
|
||||
## Release Authority
|
||||
|
||||
Only maintainers may:
|
||||
|
||||
* Cut releases and publish artifacts.
|
||||
* Update version numbers and manifests.
|
||||
* Publish update metadata or advisories.
|
||||
|
||||
Release processes follow documented workflows and automation standards.
|
||||
|
||||
## Security Governance
|
||||
|
||||
Security issues are governed by the SECURITY.md policy. Maintainers are responsible for confidential handling, coordinated disclosure, and publication of advisories when appropriate.
|
||||
|
||||
## Conduct Enforcement
|
||||
|
||||
Behavior within the project is governed by CODE_OF_CONDUCT.md. Maintainers are responsible for enforcement actions and escalation handling.
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
Conflicts are handled through:
|
||||
|
||||
* Direct discussion between involved parties when appropriate.
|
||||
* Maintainer mediation when necessary.
|
||||
* Final determination by the project owner if required.
|
||||
|
||||
## External Dependencies
|
||||
|
||||
The project depends on Joomla core and other third party components. Governance of upstream projects remains outside the scope of this repository, but upstream changes may influence project decisions.
|
||||
|
||||
## Jurisdiction
|
||||
|
||||
This project is managed from Tennessee, USA. This statement is informational and does not constitute legal advice.
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* **Document:** GOVERNANCE.md
|
||||
* **Repository:** [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* **Path:** /GOVERNANCE.md
|
||||
* **Owner:** Moko Consulting
|
||||
* **Version:** 03.06.00
|
||||
* **Status:** Active
|
||||
* **Effective Date:** 2025-12-18
|
||||
* **Last Reviewed:** 2025-12-18
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ----------------------------------------------------------------------- | --------------- |
|
||||
| 2025-12-18 | Initial publication of governance model, roles, and decision processes. | Moko Consulting |
|
||||
696
LICENSE
Normal file
696
LICENSE
Normal file
@@ -0,0 +1,696 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License (./LICENSE).
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: MokoStandards
|
||||
INGROUP: MokoStandards.Documentation
|
||||
REPO: https://github.com/mokoconsulting-tech/MokoStandards/
|
||||
VERSION: 05.00.00
|
||||
PATH: ./LICENSE
|
||||
BRIEF: Project license (GPL-3.0-or-later)
|
||||
NOTE: Exact text fetched from gnu.org
|
||||
-->
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
199
Makefile
Normal file
199
Makefile
Normal file
@@ -0,0 +1,199 @@
|
||||
# Makefile for MokoOnyx Joomla Template
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Build and validation powered by MokoStandards Enterprise API
|
||||
# Install: composer install
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
|
||||
EXTENSION_NAME := mokoonyx
|
||||
EXTENSION_TYPE := template
|
||||
EXTENSION_VERSION := $(shell grep -oP 'VERSION:\s*\K[0-9.]+' README.md 2>/dev/null || echo "0.0.0")
|
||||
|
||||
SRC_DIR := src
|
||||
BUILD_DIR := build
|
||||
DIST_DIR := dist
|
||||
|
||||
PHP := php
|
||||
COMPOSER := composer
|
||||
MOKO := vendor/bin/moko
|
||||
|
||||
# Colors
|
||||
COLOR_RESET := \033[0m
|
||||
COLOR_GREEN := \033[32m
|
||||
COLOR_YELLOW := \033[33m
|
||||
COLOR_BLUE := \033[34m
|
||||
COLOR_RED := \033[31m
|
||||
|
||||
# ==============================================================================
|
||||
# TARGETS
|
||||
# ==============================================================================
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show this help message
|
||||
@echo "$(COLOR_BLUE)╔════════════════════════════════════════════════════════════╗$(COLOR_RESET)"
|
||||
@echo "$(COLOR_BLUE)║ MokoOnyx Template Build ║$(COLOR_RESET)"
|
||||
@echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)"
|
||||
@echo ""
|
||||
@echo "Extension: $(EXTENSION_NAME) ($(EXTENSION_TYPE)) v$(EXTENSION_VERSION)"
|
||||
@echo "Powered by: MokoStandards Enterprise API"
|
||||
@echo ""
|
||||
@echo "$(COLOR_GREEN)Available targets:$(COLOR_RESET)"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}'
|
||||
@echo ""
|
||||
|
||||
# ── Dependencies ──────────────────────────────────────────────────────────────
|
||||
|
||||
.PHONY: install-deps
|
||||
install-deps: ## Install Composer dependencies (includes MokoStandards API)
|
||||
@echo "$(COLOR_BLUE)Installing dependencies...$(COLOR_RESET)"
|
||||
@$(COMPOSER) install
|
||||
@echo "$(COLOR_GREEN)✓ Dependencies installed$(COLOR_RESET)"
|
||||
|
||||
.PHONY: update-deps
|
||||
update-deps: ## Update Composer dependencies
|
||||
@echo "$(COLOR_BLUE)Updating dependencies...$(COLOR_RESET)"
|
||||
@$(COMPOSER) update
|
||||
@echo "$(COLOR_GREEN)✓ Dependencies updated$(COLOR_RESET)"
|
||||
|
||||
# ── Validation (MokoStandards API) ────────────────────────────────────────────
|
||||
|
||||
.PHONY: check-moko
|
||||
check-moko:
|
||||
@if [ ! -f "$(MOKO)" ]; then \
|
||||
echo "$(COLOR_RED)✗ MokoStandards CLI not found. Run: make install-deps$(COLOR_RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: lint
|
||||
lint: check-moko ## PHP syntax check via MokoStandards
|
||||
@echo "$(COLOR_BLUE)Running PHP syntax check...$(COLOR_RESET)"
|
||||
@$(PHP) $(MOKO) check:syntax -- --path .
|
||||
@echo "$(COLOR_GREEN)✓ PHP syntax OK$(COLOR_RESET)"
|
||||
|
||||
.PHONY: check-joomla
|
||||
check-joomla: check-moko ## Validate Joomla manifest via MokoStandards
|
||||
@echo "$(COLOR_BLUE)Validating Joomla manifest...$(COLOR_RESET)"
|
||||
@$(PHP) $(MOKO) check:joomla -- --path .
|
||||
@echo "$(COLOR_GREEN)✓ Joomla manifest valid$(COLOR_RESET)"
|
||||
|
||||
.PHONY: check-version
|
||||
check-version: check-moko ## Verify version consistency across files
|
||||
@echo "$(COLOR_BLUE)Checking version consistency...$(COLOR_RESET)"
|
||||
@$(PHP) $(MOKO) check:version -- --path .
|
||||
@echo "$(COLOR_GREEN)✓ Versions consistent$(COLOR_RESET)"
|
||||
|
||||
.PHONY: check-headers
|
||||
check-headers: check-moko ## Check license headers on source files
|
||||
@echo "$(COLOR_BLUE)Checking license headers...$(COLOR_RESET)"
|
||||
@$(PHP) $(MOKO) check:headers -- --path .
|
||||
@echo "$(COLOR_GREEN)✓ Headers OK$(COLOR_RESET)"
|
||||
|
||||
.PHONY: check-secrets
|
||||
check-secrets: check-moko ## Scan for leaked credentials
|
||||
@echo "$(COLOR_BLUE)Scanning for secrets...$(COLOR_RESET)"
|
||||
@$(PHP) $(MOKO) check:secrets -- --path .
|
||||
@echo "$(COLOR_GREEN)✓ No secrets found$(COLOR_RESET)"
|
||||
|
||||
.PHONY: check-xml
|
||||
check-xml: check-moko ## Validate XML files are well-formed
|
||||
@echo "$(COLOR_BLUE)Checking XML files...$(COLOR_RESET)"
|
||||
@$(PHP) $(MOKO) check:xml -- --path .
|
||||
@echo "$(COLOR_GREEN)✓ XML well-formed$(COLOR_RESET)"
|
||||
|
||||
.PHONY: validate
|
||||
validate: lint check-joomla check-version check-xml check-headers check-secrets ## Run all MokoStandards validation checks
|
||||
@echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)"
|
||||
|
||||
.PHONY: health
|
||||
health: check-moko ## Full repository health check via MokoStandards
|
||||
@echo "$(COLOR_BLUE)Running full health check...$(COLOR_RESET)"
|
||||
@$(PHP) $(MOKO) health -- --path .
|
||||
|
||||
# ── Build ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Clean build artifacts
|
||||
@echo "$(COLOR_BLUE)Cleaning build artifacts...$(COLOR_RESET)"
|
||||
@rm -rf $(BUILD_DIR) $(DIST_DIR)
|
||||
@echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)"
|
||||
|
||||
.PHONY: build
|
||||
build: clean ## Build template installable ZIP from src/
|
||||
@echo "$(COLOR_BLUE)Building $(EXTENSION_NAME) v$(EXTENSION_VERSION)...$(COLOR_RESET)"
|
||||
@mkdir -p $(BUILD_DIR)/package $(DIST_DIR)
|
||||
@cp -r $(SRC_DIR)/* $(BUILD_DIR)/package/
|
||||
@cd $(BUILD_DIR)/package && \
|
||||
if command -v zip >/dev/null 2>&1; then \
|
||||
zip -r "../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" .; \
|
||||
elif command -v pwsh >/dev/null 2>&1; then \
|
||||
pwsh -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip' -Force"; \
|
||||
elif command -v powershell >/dev/null 2>&1; then \
|
||||
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip' -Force"; \
|
||||
else \
|
||||
echo "$(COLOR_RED)✗ No zip tool found (zip, pwsh, powershell)$(COLOR_RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "$(COLOR_GREEN)✓ Package: $(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
|
||||
|
||||
.PHONY: build-beta
|
||||
build-beta: clean ## Build beta release ZIP
|
||||
@echo "$(COLOR_BLUE)Building $(EXTENSION_NAME) v$(EXTENSION_VERSION)-beta...$(COLOR_RESET)"
|
||||
@mkdir -p $(BUILD_DIR)/package $(DIST_DIR)
|
||||
@cp -r $(SRC_DIR)/* $(BUILD_DIR)/package/
|
||||
@cd $(BUILD_DIR)/package && \
|
||||
if command -v zip >/dev/null 2>&1; then \
|
||||
zip -r "../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip" .; \
|
||||
elif command -v pwsh >/dev/null 2>&1; then \
|
||||
pwsh -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip' -Force"; \
|
||||
elif command -v powershell >/dev/null 2>&1; then \
|
||||
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip' -Force"; \
|
||||
else \
|
||||
echo "$(COLOR_RED)✗ No zip tool found$(COLOR_RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "$(COLOR_GREEN)✓ Package: $(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip$(COLOR_RESET)"
|
||||
|
||||
.PHONY: checksum
|
||||
checksum: ## Generate SHA-256 checksums for dist packages
|
||||
@echo "$(COLOR_BLUE)Generating checksums...$(COLOR_RESET)"
|
||||
@for f in $(DIST_DIR)/*.zip; do \
|
||||
sha256sum "$$f" | tee "$${f}.sha256"; \
|
||||
done
|
||||
@echo "$(COLOR_GREEN)✓ Checksums generated$(COLOR_RESET)"
|
||||
|
||||
# ── Release ───────────────────────────────────────────────────────────────────
|
||||
|
||||
.PHONY: release
|
||||
release: validate build checksum ## Full release pipeline (validate + build + checksum)
|
||||
@echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)"
|
||||
@echo ""
|
||||
@echo "$(COLOR_BLUE)Next steps:$(COLOR_RESET)"
|
||||
@echo " 1. Tag: git tag $(EXTENSION_VERSION)"
|
||||
@echo " 2. Push: git push origin --tags"
|
||||
@echo " 3. Create Gitea release and attach $(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip"
|
||||
@echo ""
|
||||
|
||||
# ── Info ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
.PHONY: version
|
||||
version: ## Display version and extension info
|
||||
@echo "$(COLOR_BLUE)Extension Information:$(COLOR_RESET)"
|
||||
@echo " Name: $(EXTENSION_NAME)"
|
||||
@echo " Type: $(EXTENSION_TYPE)"
|
||||
@echo " Version: $(EXTENSION_VERSION)"
|
||||
|
||||
.PHONY: security-check
|
||||
security-check: ## Run Composer security audit
|
||||
@echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)"
|
||||
@$(COMPOSER) audit
|
||||
@echo "$(COLOR_GREEN)✓ Security check complete$(COLOR_RESET)"
|
||||
|
||||
.PHONY: all
|
||||
all: install-deps validate build checksum ## Full pipeline: deps → validate → build → checksum
|
||||
@echo "$(COLOR_GREEN)✓ Complete build pipeline finished$(COLOR_RESET)"
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
521
README.md
521
README.md
@@ -1,3 +1,520 @@
|
||||
# MokoOnyx
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
MokoOnyx <20> Joomla site template (successor to MokoCassiopeia)
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoOnyx.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: ./README.md
|
||||
VERSION: 01.00.00
|
||||
BRIEF: Documentation for MokoOnyx template
|
||||
-->
|
||||
|
||||
# MokoOnyx → MokoOnyx
|
||||
|
||||
> **This template is being renamed to MokoOnyx.** Version 01.00.00 is the bridge release that automatically migrates your settings. After updating, MokoOnyx will be your active template and MokoOnyx can be safely uninstalled.
|
||||
|
||||
**A Modern, Lightweight Joomla Template Based on Cassiopeia**
|
||||
|
||||
[](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/v03)
|
||||
[](LICENSE)
|
||||
[](https://www.joomla.org)
|
||||
[](https://www.php.net)
|
||||
|
||||
MokoOnyx is a modern, lightweight enhancement layer built on top of Joomla's Cassiopeia template. It adds **Font Awesome 7**, **Bootstrap 5** helpers, an automatic **Table of Contents (TOC)** utility, advanced **Dark Mode** theming, and optional integrations for **Google Tag Manager** and **Google Analytics (GA4)**—all while maintaining minimal core template overrides for maximum upgrade compatibility.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Table of Contents
|
||||
|
||||
- [Features](#-features)
|
||||
- [Requirements](#-requirements)
|
||||
- [Installation](#-installation)
|
||||
- [Quick Start](#-quick-start)
|
||||
- [Configuration](#️-configuration)
|
||||
- [Theme System](#-theme-system)
|
||||
- [Development](#-development)
|
||||
- [Documentation](#-documentation)
|
||||
- [Changelog](#-changelog)
|
||||
- [Support](#-support)
|
||||
- [Contributing](#-contributing)
|
||||
- [Included Libraries](#-included-libraries)
|
||||
- [License](#-license)
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
### Core Enhancements
|
||||
|
||||
- **Built on Cassiopeia**: Extends Joomla's default template with minimal overrides
|
||||
- **Font Awesome 7**: Fully integrated into Joomla's asset manager with 2,000+ icons
|
||||
- **Bootstrap 5**: Extended utility classes and responsive grid system
|
||||
- **No Template Overrides**: Clean installation that inherits all Cassiopeia defaults
|
||||
- **Upgrade-Friendly**: Minimal modifications ensure smooth Joomla updates
|
||||
|
||||
### Advanced Theming
|
||||
|
||||
- **Dark Mode Support**: Built-in light/dark mode toggle with system preference detection
|
||||
- **Color Palettes**: Standard, Alternative, and Custom color schemes
|
||||
- **Theme Persistence**: User preferences saved via localStorage
|
||||
- **Theme Control Options**: Switch, radio buttons, or hidden controls
|
||||
- **Auto Dark Mode**: Optional automatic dark mode based on time/system settings
|
||||
- **Meta Tags**: Automatic color-scheme and theme-color meta tags
|
||||
|
||||
### Developer Features
|
||||
|
||||
- **Custom Code Injection**: Add custom HTML to `<head>` start/end
|
||||
- **Drawer Sidebars**: Configurable left/right drawer positions with custom icons
|
||||
- **Font Options**: Local and web fonts (Roboto, Fira Sans, Noto Sans)
|
||||
- **Sticky Header**: Optional sticky navigation
|
||||
- **Back to Top**: Floating back-to-top button
|
||||
|
||||
### Analytics & Tracking
|
||||
|
||||
- **Google Tag Manager**: Optional GTM integration with container ID configuration
|
||||
- **Google Analytics**: Optional GA4 integration with measurement ID
|
||||
- **Privacy-Friendly**: All tracking features are optional and easily disabled
|
||||
|
||||
### Content Features
|
||||
|
||||
- **Table of Contents**: Automatic TOC generation for long articles
|
||||
- Placement options: `toc-left` or `toc-right` layouts
|
||||
- Automatic heading extraction and navigation
|
||||
- Responsive sidebar positioning
|
||||
|
||||
---
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
- **Joomla**: 4.4.x or 5.x
|
||||
- **PHP**: 8.0 or higher
|
||||
- **Database**: MySQL 5.7+ / MariaDB 10.2+ / PostgreSQL 11+
|
||||
- **Browser Support**: Modern browsers (Chrome, Firefox, Safari, Edge)
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
**Note**: MokoOnyx is a **standalone Joomla template extension** (not bundled as a package). Install it directly via Joomla's Extension Manager.
|
||||
|
||||
### Via Joomla Extension Manager
|
||||
|
||||
1. Download the latest `mokoonyx-{version}.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases)
|
||||
2. In Joomla admin, navigate to **System → Install → Extensions**
|
||||
3. Upload the ZIP file and click **Upload & Install**
|
||||
4. Navigate to **System → Site Templates**
|
||||
5. Set **MokoOnyx** as your default template
|
||||
|
||||
### Via Git (Development)
|
||||
|
||||
```bash
|
||||
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx.git
|
||||
cd MokoOnyx
|
||||
```
|
||||
|
||||
See [Development Guide](./docs/JOOMLA_DEVELOPMENT.md) for development setup.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Install the Template
|
||||
|
||||
Install `mokoonyx.zip` via Joomla's Extension Manager.
|
||||
|
||||
### 2. Set as Default
|
||||
|
||||
Navigate to **System → Site Templates** and set **MokoOnyx** as default.
|
||||
|
||||
### 3. Configure Template Options
|
||||
|
||||
Go to **System → Site Templates → MokoOnyx** to configure:
|
||||
|
||||
- **Branding**: Upload logo, set site title/description
|
||||
- **Theme**: Configure color schemes and dark mode
|
||||
- **Layout**: Set container type (static/fluid), sticky header
|
||||
- **Analytics**: Add GTM/GA4 tracking codes (optional)
|
||||
- **Custom Code**: Inject custom HTML/CSS/JS
|
||||
|
||||
### 4. Test Dark Mode
|
||||
|
||||
The template includes a dark mode toggle. Test it by:
|
||||
- Using the floating theme toggle button (bottom-right by default)
|
||||
- Checking theme persistence across page loads
|
||||
- Verifying system preference detection
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed and set as the default site template, MokoOnyx works out of the box with Joomla's standard content and module system. Key usage points:
|
||||
|
||||
- **Template Options** — Configure via **System → Site Templates → MokoOnyx** (theme colours, layout, analytics, favicon, drawers)
|
||||
- **Custom Colour Schemes** — Copy `templates/mokoonyx/templates/light.custom.css` or `dark.custom.css` to `media/templates/site/mokoonyx/css/theme/` and select "Custom" in the Theme tab
|
||||
- **Custom CSS/JS** — Create `media/templates/site/mokoonyx/css/user.css` or `js/user.js` for site-specific overrides that survive template updates
|
||||
- **Module Overrides** — The template includes overrides for common Joomla modules with consistent title rendering, Bootstrap 5 styling, and Font Awesome 7 icons
|
||||
- **Dark Mode** — Enabled by default with a floating toggle button; respects system preference and persists via localStorage
|
||||
|
||||
See [Configuration](#️-configuration) below for detailed parameter reference.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Global Parameters
|
||||
|
||||
Access template configuration via **System → Site Templates → MokoOnyx**.
|
||||
|
||||
#### Theme Tab
|
||||
|
||||
**General Settings:**
|
||||
- **Theme Enabled**: Enable/disable theme system
|
||||
- **Theme Control Type**: Switch (Light↔Dark), Radios (Light/Dark/System), or None
|
||||
- **Default Choice**: System, Light, or Dark
|
||||
- **Auto Dark Mode**: Automatic dark mode based on time
|
||||
- **Meta Tags**: Enable color-scheme and theme-color meta tags
|
||||
- **Bridge Bootstrap ARIA**: Sync theme with Bootstrap's data-bs-theme
|
||||
|
||||
**Variables & Palettes:**
|
||||
- **Light Mode Palette**: Standard, Alternative, or Custom
|
||||
- **Dark Mode Palette**: Standard, Alternative, or Custom
|
||||
|
||||
**Typography:**
|
||||
- **Font Scheme**: Local (Roboto) or Web fonts (Fira Sans, Roboto+Noto Sans)
|
||||
|
||||
**Branding & Icons:**
|
||||
- **Brand**: Enable/disable site branding
|
||||
- **Logo File**: Upload custom logo (no default logo included)
|
||||
- **Site Title**: Custom site title
|
||||
- **Site Description**: Tagline/description
|
||||
- **Font Awesome Kit**: Optional FA Pro kit code
|
||||
|
||||
**Header & Navigation:**
|
||||
- **Sticky Header**: Enable fixed header on scroll
|
||||
- **Back to Top**: Enable floating back-to-top button
|
||||
|
||||
**Theme Toggle UI:**
|
||||
- **FAB Enabled**: Enable floating action button toggle
|
||||
- **FAB Position**: Bottom-right, Bottom-left, Top-right, or Top-left
|
||||
|
||||
#### Advanced Tab
|
||||
|
||||
- **Layout**: Static or Fluid container
|
||||
|
||||
#### Google Tab
|
||||
|
||||
- **Google Tag Manager**: Enable and configure GTM container ID
|
||||
- **Google Analytics**: Enable and configure GA4 measurement ID
|
||||
|
||||
#### Custom Code Tab
|
||||
|
||||
- **Custom Head Start**: HTML injected at start of `<head>`
|
||||
- **Custom Head End**: HTML injected at end of `<head>`
|
||||
|
||||
#### Drawers Tab
|
||||
|
||||
- **Left Drawer Icon**: Font Awesome icon class (e.g., `fa-solid fa-chevron-right`)
|
||||
- **Right Drawer Icon**: Font Awesome icon class (e.g., `fa-solid fa-chevron-left`)
|
||||
|
||||
### Custom Theme Palettes
|
||||
|
||||
MokoOnyx supports custom theme schemes:
|
||||
|
||||
1. **Copy template files** from `/templates/` directory:
|
||||
- `light.custom.css` → `media/templates/site/mokoonyx/css/theme/light.custom.css`
|
||||
- `dark.custom.css` → `media/templates/site/mokoonyx/css/theme/dark.custom.css`
|
||||
2. **Customize** the CSS variables to match your brand colors
|
||||
3. **Enable in Joomla**: System → Site Templates → MokoOnyx → Theme tab → Set palette to "Custom"
|
||||
4. **Save** and view your site with custom colors
|
||||
|
||||
**Note:** Custom color files are excluded from version control (`.gitignore`) to prevent fork-specific customizations from being committed.
|
||||
|
||||
**Quick Example:**
|
||||
|
||||
```css
|
||||
:root[data-bs-theme="light"] {
|
||||
--color-primary: #1e40af;
|
||||
--color-link: #2563eb;
|
||||
--color-hover: #1d4ed8;
|
||||
--body-color: #1f2937;
|
||||
--body-bg: #ffffff;
|
||||
}
|
||||
```
|
||||
|
||||
**Complete Reference:** See [CSS Variables Documentation](./docs/CSS_VARIABLES.md) for all available variables and detailed usage examples.
|
||||
|
||||
### Table of Contents
|
||||
|
||||
Enable automatic TOC for articles:
|
||||
|
||||
1. Edit an article in Joomla admin
|
||||
2. Navigate to **Options → Layout**
|
||||
3. Select **toc-left** or **toc-right**
|
||||
4. Save the article
|
||||
|
||||
The TOC will automatically generate from article headings (H2, H3, etc.) and appear as a sidebar.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Theme System
|
||||
|
||||
### Dark Mode Features
|
||||
|
||||
- **Automatic Detection**: Respects user's system preferences
|
||||
- **Manual Toggle**: Floating button or radio controls
|
||||
- **Persistence**: Saves preference in localStorage
|
||||
- **Smooth Transitions**: Animated theme switching
|
||||
- **Comprehensive Support**: All components themed for dark mode
|
||||
|
||||
### Theme Control Types
|
||||
|
||||
1. **Switch**: Simple light/dark toggle button
|
||||
2. **Radios**: Three options - Light, Dark, System
|
||||
3. **None**: No visible control (respects system only)
|
||||
|
||||
### Meta Tags
|
||||
|
||||
When enabled, the template adds:
|
||||
|
||||
```html
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="theme-color" content="#1e3a8a" media="(prefers-color-scheme: dark)">
|
||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Development
|
||||
|
||||
### For Contributors
|
||||
|
||||
**New to the project?** See [Quick Start Guide](./docs/QUICK_START.md) for a 5-minute setup.
|
||||
|
||||
### Development Resources
|
||||
|
||||
- **[Quick Start Guide](./docs/QUICK_START.md)** - Setup and first steps
|
||||
- **[Joomla Development Guide](./docs/JOOMLA_DEVELOPMENT.md)** - Testing, quality checks, deployment
|
||||
- **[Workflow Guide](./docs/WORKFLOW_GUIDE.md)** - Git workflow and branching
|
||||
- **[Contributing Guide](./CONTRIBUTING.md)** - Contribution guidelines
|
||||
- **[Roadmap](./docs/ROADMAP.md)** - Feature roadmap and planning
|
||||
|
||||
### Development Tools
|
||||
|
||||
- **Pre-commit Hooks**: Automatic validation before commits
|
||||
- **PHP CodeSniffer**: Code style validation (Joomla standards)
|
||||
- **PHPStan**: Static analysis for PHP code
|
||||
- **Codeception**: Testing framework
|
||||
|
||||
### Quick Development Setup
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx.git
|
||||
cd MokoOnyx
|
||||
|
||||
# Install development dependencies (if using Composer)
|
||||
composer install --dev
|
||||
|
||||
# Run code quality checks
|
||||
make validate # or manual commands
|
||||
```
|
||||
|
||||
### Building Template Package
|
||||
|
||||
See [Joomla Development Guide](./docs/JOOMLA_DEVELOPMENT.md) for packaging instructions.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### User Documentation
|
||||
|
||||
- **[README](./README.md)** - This file (overview and features)
|
||||
- **[CHANGELOG](./CHANGELOG.md)** - Version history and changes
|
||||
- **[Roadmap](./docs/ROADMAP.md)** - Planned features and timeline
|
||||
|
||||
### Developer Documentation
|
||||
|
||||
- **[Quick Start](./docs/QUICK_START.md)** - 5-minute developer setup
|
||||
- **[Development Guide](./docs/JOOMLA_DEVELOPMENT.md)** - Comprehensive development guide
|
||||
- **[Workflow Guide](./docs/WORKFLOW_GUIDE.md)** - Git workflow and processes
|
||||
- **[CSS Variables Reference](./docs/CSS_VARIABLES.md)** - Complete CSS customization guide
|
||||
- **[Documentation Index](./docs/README.md)** - All documentation links
|
||||
|
||||
### Governance
|
||||
|
||||
- **[Contributing](./CONTRIBUTING.md)** - How to contribute
|
||||
- **[Code of Conduct](./CODE_OF_CONDUCT.md)** - Community standards
|
||||
- **[Governance](./GOVERNANCE.md)** - Project governance model
|
||||
- **[Security Policy](./SECURITY.md)** - Security reporting and procedures
|
||||
|
||||
---
|
||||
|
||||
## 📖 Changelog
|
||||
|
||||
See the [CHANGELOG.md](./CHANGELOG.md) for detailed version history.
|
||||
|
||||
### Recent Releases
|
||||
|
||||
- **[03.06.03]** (2026-01-30) - README updates and TOC color variable improvements
|
||||
- **[03.06.02]** (2026-01-30) - Complete rebrand to MokoOnyx, removed all overrides
|
||||
- **[03.06.00]** (2026-01-28) - Version standardization
|
||||
- **[03.05.01]** (2026-01-09) - Security and compliance improvements
|
||||
- **[02.00.00]** (2025-08-30) - Dark mode toggle and improved theming
|
||||
|
||||
---
|
||||
|
||||
## 💬 Support
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Documentation**: Check this README and [docs folder](./docs/)
|
||||
- **Issues**: Report bugs via [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||
- **Discussions**: Ask questions in [GitHub Discussions](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/discussions)
|
||||
- **Roadmap**: View planned features in [Roadmap](https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap)
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
Please include:
|
||||
- Joomla version
|
||||
- PHP version
|
||||
- Template version
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behavior
|
||||
- Screenshots (if applicable)
|
||||
|
||||
### Security Issues
|
||||
|
||||
**Do not** report security vulnerabilities via public issues. See [SECURITY.md](./SECURITY.md) for reporting procedures.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
||||
|
||||
### How to Contribute
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Make your changes
|
||||
4. Run quality checks
|
||||
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||
6. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
7. Open a Pull Request
|
||||
|
||||
### Development Workflow
|
||||
|
||||
See [Workflow Guide](./docs/WORKFLOW_GUIDE.md) for detailed Git workflow.
|
||||
|
||||
### Customizations
|
||||
|
||||
For template customizations, use Joomla's built-in template settings (System → Site Templates → MokoOnyx → Custom Code tab) for HTML/CSS/JS customizations.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Included Libraries
|
||||
|
||||
MokoOnyx includes the following third-party libraries to provide enhanced functionality:
|
||||
|
||||
### Bootstrap TOC
|
||||
|
||||
- **Version**: 1.0.1
|
||||
- **Author**: Aidan Feldman
|
||||
- **License**: MIT License
|
||||
- **Source**: [GitHub Repository](https://github.com/afeld/bootstrap-toc)
|
||||
- **Release**: [v1.0.1 Release](https://github.com/afeld/bootstrap-toc/releases/tag/v1.0.1)
|
||||
- **Purpose**: Automatically generates a table of contents from article headings with scrollspy support
|
||||
- **Location**: `src/media/vendor/bootstrap-toc/`
|
||||
- **Integration**: Registered in `joomla.asset.json` as `vendor.bootstrap-toc` (CSS) and `vendor.bootstrap-toc.js` (JavaScript)
|
||||
- **Usage**: Activated when using `toc-left` or `toc-right` article layouts
|
||||
- **Features**:
|
||||
- Automatic TOC generation from H1-H6 headings
|
||||
- Hierarchical nested navigation
|
||||
- Active state highlighting with scrollspy
|
||||
- Responsive design (collapses on mobile)
|
||||
- Smooth scrolling to sections
|
||||
- Automatic unique ID generation for headings
|
||||
- **Customizations**: CSS adapted to use Cassiopeia CSS variables for theme compatibility
|
||||
|
||||
### Font Awesome 7 Free
|
||||
|
||||
- **Version**: 7.0 (Free)
|
||||
- **License**: Font Awesome Free License
|
||||
- **Source**: [Font Awesome](https://fontawesome.com)
|
||||
- **Purpose**: Provides 2,000+ vector icons for interface elements
|
||||
- **Location**: `src/media/vendor/fa7free/`
|
||||
- **Integration**: Fully integrated into Joomla's asset manager
|
||||
- **Styles Available**: Solid, Regular, Brands
|
||||
|
||||
### Bootstrap 5
|
||||
|
||||
- **Version**: 5.x (via Joomla)
|
||||
- **License**: MIT License
|
||||
- **Source**: [Bootstrap](https://getbootstrap.com)
|
||||
- **Purpose**: Provides responsive grid system and utility classes
|
||||
- **Integration**: Inherited from Joomla's Cassiopeia template, extended with additional helpers
|
||||
- **Components Used**: Grid, utilities, modal, dropdown, collapse, offcanvas, tooltip, popover, scrollspy
|
||||
|
||||
### Integration Method
|
||||
|
||||
All third-party libraries are:
|
||||
- ✅ Properly licensed and attributed
|
||||
- ✅ Registered in Joomla's Web Asset Manager (`joomla.asset.json`)
|
||||
- ✅ Loaded on-demand to optimize performance
|
||||
- ✅ Versioned and documented for maintenance
|
||||
- ✅ Compatible with Joomla 4.4.x and 5.x
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the **GNU General Public License v3.0** - see the [LICENSE](./LICENSE) file for details.
|
||||
|
||||
### Third-Party Licenses
|
||||
|
||||
- **Joomla! CMS**: GPL-2.0-or-later
|
||||
- **Cassiopeia Template**: GPL-2.0-or-later (Joomla Project)
|
||||
- **Font Awesome 7 Free**: Font Awesome Free License
|
||||
- **Bootstrap 5**: MIT License
|
||||
- **Bootstrap TOC**: MIT License (A. Feld)
|
||||
|
||||
All third-party libraries and assets remain the property of their respective authors and are credited in source files.
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- **Repository**: [GitHub](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
- **Issue Tracker**: [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||
- **Discussions**: [GitHub Discussions](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/discussions)
|
||||
- **Roadmap**: [Full Roadmap](https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap)
|
||||
- **Moko Consulting**: [Website](https://mokoconsulting.tech)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Metadata
|
||||
|
||||
- **Maintainer**: Moko Consulting Engineering
|
||||
- **Author**: Jonathan Miller (@jmiller-moko)
|
||||
- **Repository**: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
- **License**: GPL-3.0-or-later
|
||||
- **Classification**: Public Open Source Standards
|
||||
|
||||
## 📝 Revision History
|
||||
|
||||
| Date | Version | Change Summary | Author |
|
||||
| ---------- | -------- | ------------------------------------------------------------------------- | ------------------------------- |
|
||||
| 2026-01-30 | 03.06.03 | Updated README title, fixed custom color variables instructions, improved TOC color scheme integration | Copilot Agent |
|
||||
| 2026-01-30 | 03.06.02 | Regenerated README with comprehensive documentation and updated structure | Copilot Agent |
|
||||
| 2026-01-30 | 03.06.02 | Complete rebrand to MokoOnyx, removed overrides | Copilot Agent |
|
||||
| 2026-01-05 | 03.00.00 | Initial publication of template documentation and feature overview | Moko Consulting |
|
||||
| 2026-01-05 | 03.00.00 | Fixed malformed markdown table formatting in revision history | Jonathan Miller (@jmiller-moko) |
|
||||
|
||||
---
|
||||
|
||||
**Made with ❤️ by [Moko Consulting](https://mokoconsulting.tech)**
|
||||
|
||||
185
SECURITY.md
Normal file
185
SECURITY.md
Normal file
@@ -0,0 +1,185 @@
|
||||
<!--
|
||||
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template
|
||||
INGROUP: MokoOnyx.Governance
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: SECURITY.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Security policy and vulnerability reporting process for MokoOnyx.
|
||||
PATH: /SECURITY.md
|
||||
NOTE: This policy is process oriented and does not replace secure engineering practices.
|
||||
-->
|
||||
|
||||
## Security Policy
|
||||
|
||||
This document defines how MokoOnyx handles vulnerability intake, triage, remediation, and disclosure. The objective is to reduce risk, protect downstream users, and preserve operational continuity with a verifiable audit trail.
|
||||
|
||||
## Scope
|
||||
|
||||
This policy applies to:
|
||||
|
||||
* Repository source code, workflows, scripts, and build artifacts.
|
||||
* Release packaging (ZIP outputs) generated from the repository.
|
||||
* Configuration and metadata used for distribution (for example manifests and update metadata).
|
||||
|
||||
Out of scope:
|
||||
|
||||
* Vulnerabilities in upstream Joomla core, third party extensions, or external infrastructure not controlled by this repository.
|
||||
* Issues that require physical access to a host, compromised administrator credentials, or a compromised hosting provider, unless the repository materially increases impact.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Security fixes are prioritized for:
|
||||
|
||||
* The latest released version.
|
||||
* The current development line when it is actively used for release engineering.
|
||||
|
||||
Backports may be provided based on impact, deployment footprint, and engineering capacity.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use one of the following channels:
|
||||
|
||||
* GitHub Security Advisories (preferred): use the repository security tab to submit a private report.
|
||||
* Email: send details to `hello@mokoconsulting.tech` with subject `SECURITY: MokoOnyx vulnerability report`.
|
||||
|
||||
Do not file a public GitHub issue for suspected security vulnerabilities.
|
||||
|
||||
### What to include
|
||||
|
||||
Provide enough detail to reproduce and triage:
|
||||
|
||||
* A clear description of the vulnerability and expected impact.
|
||||
* A minimal proof of concept or reproduction steps.
|
||||
* Affected versions, configuration assumptions, and environment details.
|
||||
* Any proposed mitigation or patch.
|
||||
* Your preferred contact details for follow up.
|
||||
|
||||
## Triage and Response Targets
|
||||
|
||||
The project operates with response targets aligned to practical delivery realities:
|
||||
|
||||
* **Acknowledgement:** within 3 business days.
|
||||
* **Initial triage:** within 10 business days.
|
||||
* **Fix plan:** communicated once severity is confirmed.
|
||||
|
||||
These targets are not guarantees. Complex issues, supply chain considerations, and coordination with upstream vendors may extend timelines.
|
||||
|
||||
## Severity Assessment
|
||||
|
||||
Issues are triaged based on business impact and technical exploitability, including:
|
||||
|
||||
* Remote exploitability and required privileges.
|
||||
* Data confidentiality, integrity, and availability impact.
|
||||
* Likelihood of exploitation in typical Joomla deployments.
|
||||
* Exposure surface (public endpoints, administrator area, installation flows, and update mechanisms).
|
||||
|
||||
When appropriate, industry standard scoring such as CVSS may be used for internal prioritization.
|
||||
|
||||
## Coordinated Disclosure
|
||||
|
||||
The project follows coordinated vulnerability disclosure:
|
||||
|
||||
* Reports are treated as confidential until remediation is available.
|
||||
* A public advisory may be published once a fix is released.
|
||||
* A reasonable embargo period is expected to enable patch distribution.
|
||||
|
||||
If you believe disclosure is time sensitive due to active exploitation, include that assessment and any supporting indicators.
|
||||
|
||||
## Security Updates and Advisories
|
||||
|
||||
Security updates are distributed through:
|
||||
|
||||
* GitHub releases for the repository.
|
||||
* GitHub Security Advisories when applicable.
|
||||
|
||||
Advisories may include:
|
||||
|
||||
* Affected versions and fixed versions.
|
||||
* Mitigations and workarounds when a fix is not immediately available.
|
||||
* Upgrade guidance.
|
||||
|
||||
## Dependencies and Supply Chain Controls
|
||||
|
||||
The project aims to manage supply chain risk through:
|
||||
|
||||
* Pinning and review of workflow dependencies where feasible.
|
||||
* Minimizing privileged GitHub token permissions.
|
||||
* Validating build inputs prior to packaging releases.
|
||||
|
||||
If you identify a supply chain issue (for example compromised action, dependency confusion, or malicious upstream artifact), report it as a vulnerability.
|
||||
|
||||
## Secure Development and CI Expectations
|
||||
|
||||
Security posture is reinforced through operational controls:
|
||||
|
||||
* CI validation for packaging inputs and manifest integrity.
|
||||
* Consistent path normalization and whitespace hygiene checks where required for release correctness.
|
||||
* Least privilege for GitHub Actions permissions.
|
||||
|
||||
### Template Security Features
|
||||
|
||||
**Custom Head Content Injection**
|
||||
|
||||
The template provides Custom Head Code fields (`custom_head_start` and `custom_head_end`) that allow administrators to inject custom HTML, CSS, and JavaScript code. This is an intentional feature for:
|
||||
|
||||
* Adding analytics scripts (Google Analytics, Google Tag Manager)
|
||||
* Custom meta tags
|
||||
* Third-party integrations
|
||||
* Custom styling
|
||||
|
||||
**Security Considerations:**
|
||||
|
||||
* These fields use `filter="raw"` to allow HTML/JS injection
|
||||
* **Access is restricted to Joomla administrators only** via template configuration
|
||||
* This is not an XSS vulnerability as it requires administrator privileges
|
||||
* Administrators should only add trusted code from verified sources
|
||||
* Regular security audits should review custom head content
|
||||
|
||||
This policy does not guarantee that all vulnerabilities will be prevented. It defines how risk is managed when issues are discovered.
|
||||
|
||||
## Safe Harbor
|
||||
|
||||
The project supports good faith security research. When you:
|
||||
|
||||
* Avoid privacy violations, data destruction, and service disruption.
|
||||
* Limit testing to systems you own or have explicit permission to test.
|
||||
* Provide a reasonable window for coordinated disclosure.
|
||||
|
||||
Then the project will treat your report as a constructive security contribution.
|
||||
|
||||
Jurisdiction note: this repository is managed from Tennessee, USA. This note is informational only and does not constitute legal advice.
|
||||
|
||||
## Public Communications
|
||||
|
||||
Only maintainers will publish security advisories or public statements for confirmed vulnerabilities. Public communication will focus on actionable remediation and operational risk reduction.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
If you want credit, include the name or handle to list in an advisory. If you prefer anonymity, state that explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* **Document:** SECURITY.md
|
||||
* **Repository:** [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* **Path:** /SECURITY.md
|
||||
* **Owner:** Moko Consulting
|
||||
* **Version:** 03.06.00
|
||||
* **Status:** Active
|
||||
* **Effective Date:** 2025-12-18
|
||||
* **Last Reviewed:** 2025-12-18
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------ | --------------- |
|
||||
| 2026-01-30 | Added Template Security Features section documenting custom head content injection controls. | Copilot Agent |
|
||||
| 2025-12-18 | Initial publication of security policy, intake channels, triage targets, and disclosure process. | Moko Consulting |
|
||||
34
codeception.yml
Normal file
34
codeception.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace: Tests
|
||||
paths:
|
||||
tests: tests
|
||||
output: tests/_output
|
||||
data: tests/_data
|
||||
support: tests/_support
|
||||
envs: tests/_envs
|
||||
settings:
|
||||
shuffle: false
|
||||
lint: true
|
||||
colors: true
|
||||
memory_limit: 1024M
|
||||
coverage:
|
||||
enabled: true
|
||||
include:
|
||||
- src/*
|
||||
exclude:
|
||||
- src/vendor/*
|
||||
- src/media/*
|
||||
- src/language/*
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\RunFailed
|
||||
params:
|
||||
- env
|
||||
modules:
|
||||
config:
|
||||
Db:
|
||||
dsn: 'mysql:host=localhost;dbname=joomla_test'
|
||||
user: 'root'
|
||||
password: ''
|
||||
dump: tests/_data/dump.sql
|
||||
populate: true
|
||||
cleanup: true
|
||||
30
composer.json
Normal file
30
composer.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "mokoconsulting/mokoonyx",
|
||||
"description": "MokoOnyx \u00e2\u20ac\u201d Joomla site template based on Cassiopeia",
|
||||
"type": "joomla-template",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jonathan Miller",
|
||||
"email": "hello@mokoconsulting.tech"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"ext-zip": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"mokoconsulting-tech/enterprise": "^4.0"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"
|
||||
}
|
||||
]
|
||||
}
|
||||
1460
docs/CSS_VARIABLES.md
Normal file
1460
docs/CSS_VARIABLES.md
Normal file
File diff suppressed because it is too large
Load Diff
376
docs/JOOMLA_DEVELOPMENT.md
Normal file
376
docs/JOOMLA_DEVELOPMENT.md
Normal file
@@ -0,0 +1,376 @@
|
||||
# Joomla Development Workflows and Scripts
|
||||
|
||||
This document describes the Joomla-aware development workflows and scripts available in this repository.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Requirements](#requirements)
|
||||
- [Scripts](#scripts)
|
||||
- [GitHub Actions Workflows](#github-actions-workflows)
|
||||
- [Testing](#testing)
|
||||
- [Code Quality](#code-quality)
|
||||
- [Deployment](#deployment)
|
||||
|
||||
## Overview
|
||||
|
||||
This repository includes comprehensive Joomla development workflows and scripts for:
|
||||
|
||||
1. **Extension Packaging** - Create distributable ZIP packages
|
||||
2. **Joomla Testing** - Automated testing with multiple Joomla versions
|
||||
3. **Code Quality** - PHPStan, PHP_CodeSniffer, and compatibility checks
|
||||
4. **Deployment** - Staging and production deployment workflows
|
||||
|
||||
## Requirements
|
||||
|
||||
### Local Development
|
||||
|
||||
- PHP 8.0 or higher
|
||||
- Composer (for PHPStan and PHP_CodeSniffer)
|
||||
- Node.js 18+ (for some workflows)
|
||||
- MySQL/MariaDB (for Joomla testing)
|
||||
|
||||
### CI/CD (GitHub Actions)
|
||||
|
||||
All requirements are automatically installed in CI/CD pipelines.
|
||||
|
||||
## Scripts
|
||||
|
||||
### Extension Packaging
|
||||
|
||||
Package the Joomla template as a distributable ZIP file:
|
||||
|
||||
```bash
|
||||
make package
|
||||
```
|
||||
|
||||
This creates a ZIP file in the `dist` directory with all necessary template files, excluding development files.
|
||||
|
||||
## GitHub Actions Workflows
|
||||
|
||||
### 1. PHP Code Quality (`php_quality.yml`)
|
||||
|
||||
Runs on every push and pull request to main branches.
|
||||
|
||||
**Jobs:**
|
||||
- **PHP_CodeSniffer** - Checks code style and standards
|
||||
- **PHPStan** - Static analysis at level 5
|
||||
- **PHP Compatibility** - Ensures PHP 8.0+ compatibility
|
||||
|
||||
**Matrix Testing:**
|
||||
- PHP versions: 8.0, 8.1, 8.2, 8.3
|
||||
|
||||
**Trigger:**
|
||||
```bash
|
||||
# Automatically runs on push/PR
|
||||
git push origin dev/3.5.0
|
||||
```
|
||||
|
||||
### 2. Joomla Testing (`joomla_testing.yml`)
|
||||
|
||||
Tests template with multiple Joomla and PHP versions.
|
||||
|
||||
**Jobs:**
|
||||
- **Joomla Setup** - Installs Joomla CMS
|
||||
- **Template Installation** - Installs template into Joomla
|
||||
- **Validation** - Validates template functionality
|
||||
- **Codeception** - Runs test framework
|
||||
|
||||
**Matrix Testing:**
|
||||
- PHP versions: 8.0, 8.1, 8.2, 8.3
|
||||
- Joomla versions: 4.4 (LTS), 5.0, 5.1
|
||||
- MySQL version: 8.0
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Automatically runs on push to main branches
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 3. Deploy to Staging (`deploy_staging.yml`)
|
||||
|
||||
Manual deployment to staging/development environments.
|
||||
|
||||
**Parameters:**
|
||||
- `environment`: Target environment (staging, development, preview)
|
||||
- `version`: Version to deploy (optional, defaults to latest)
|
||||
|
||||
**Usage:**
|
||||
1. Go to Actions → Deploy to Staging
|
||||
2. Click "Run workflow"
|
||||
3. Select environment and version
|
||||
4. Click "Run workflow"
|
||||
|
||||
**Required Secrets:**
|
||||
For staging deployment, configure these repository secrets:
|
||||
- `STAGING_HOST` - SFTP server hostname
|
||||
- `STAGING_USER` - SFTP username
|
||||
- `STAGING_KEY` - SSH private key (recommended) or use `STAGING_PASSWORD`
|
||||
- `STAGING_PATH` - Remote path for deployment
|
||||
- `STAGING_PORT` - SSH port (optional, default: 22)
|
||||
|
||||
## Testing
|
||||
|
||||
### Codeception Framework
|
||||
|
||||
The repository is configured with Codeception for acceptance and unit testing.
|
||||
|
||||
#### Running Tests Locally
|
||||
|
||||
1. Install Codeception:
|
||||
```bash
|
||||
composer global require "codeception/codeception" --with-all-dependencies
|
||||
```
|
||||
|
||||
2. Run tests:
|
||||
```bash
|
||||
# Run all tests
|
||||
codecept run
|
||||
|
||||
# Run acceptance tests only
|
||||
codecept run acceptance
|
||||
|
||||
# Run unit tests only
|
||||
codecept run unit
|
||||
|
||||
# Run with verbose output
|
||||
codecept run --debug
|
||||
```
|
||||
|
||||
#### Test Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── _data/ # Test data and fixtures
|
||||
├── _output/ # Test reports and screenshots
|
||||
├── _support/ # Helper classes
|
||||
├── acceptance/ # Acceptance tests
|
||||
│ └── TemplateInstallationCest.php
|
||||
├── unit/ # Unit tests
|
||||
│ └── TemplateConfigurationTest.php
|
||||
├── acceptance.suite.yml
|
||||
└── unit.suite.yml
|
||||
```
|
||||
|
||||
#### Writing Tests
|
||||
|
||||
**Unit Test Example:**
|
||||
```php
|
||||
<?php
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Codeception\Test\Unit;
|
||||
|
||||
class MyTemplateTest extends Unit
|
||||
{
|
||||
public function testSomething()
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Acceptance Test Example:**
|
||||
```php
|
||||
<?php
|
||||
namespace Tests\Acceptance;
|
||||
|
||||
use Tests\Support\AcceptanceTester;
|
||||
|
||||
class MyAcceptanceCest
|
||||
{
|
||||
public function testPageLoad(AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage('/');
|
||||
$I->see('Welcome');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Code Quality
|
||||
|
||||
### PHPStan
|
||||
|
||||
Static analysis configuration in `phpstan.neon`:
|
||||
|
||||
```bash
|
||||
# Run PHPStan locally
|
||||
phpstan analyse --configuration=phpstan.neon
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
- Analysis level: 5
|
||||
- Target paths: `src/`
|
||||
- PHP version: 8.0+
|
||||
|
||||
### PHP_CodeSniffer
|
||||
|
||||
Coding standards configuration in `phpcs.xml`:
|
||||
|
||||
```bash
|
||||
# Check code style
|
||||
phpcs --standard=phpcs.xml
|
||||
|
||||
# Fix auto-fixable issues
|
||||
phpcbf --standard=phpcs.xml
|
||||
```
|
||||
|
||||
**Standards:**
|
||||
- PSR-12 as base
|
||||
- PHP 8.0+ compatibility checks
|
||||
- Joomla coding conventions (when available)
|
||||
|
||||
### Running Quality Checks Locally
|
||||
|
||||
1. Install tools:
|
||||
```bash
|
||||
composer global require "squizlabs/php_codesniffer:^3.0" --with-all-dependencies
|
||||
composer global require "phpstan/phpstan:^1.0" --with-all-dependencies
|
||||
composer global require "phpcompatibility/php-compatibility:^9.0" --with-all-dependencies
|
||||
```
|
||||
|
||||
2. Configure PHPCompatibility:
|
||||
```bash
|
||||
phpcs --config-set installed_paths ~/.composer/vendor/phpcompatibility/php-compatibility
|
||||
```
|
||||
|
||||
3. Run checks:
|
||||
```bash
|
||||
# PHP syntax check
|
||||
make validate-required
|
||||
|
||||
# CodeSniffer
|
||||
phpcs --standard=phpcs.xml src/
|
||||
|
||||
# PHPStan
|
||||
phpstan analyse --configuration=phpstan.neon
|
||||
|
||||
# PHP Compatibility
|
||||
phpcs --standard=PHPCompatibility --runtime-set testVersion 8.0- src/
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Manual Deployment
|
||||
|
||||
Use the package script to create a distribution:
|
||||
|
||||
```bash
|
||||
# Create package
|
||||
make package
|
||||
|
||||
# Upload to server
|
||||
scp dist/moko-cassiopeia-3.5.0-template.zip user@server:/path/to/joomla/
|
||||
```
|
||||
|
||||
### Automated Deployment
|
||||
|
||||
Use the GitHub Actions workflow:
|
||||
|
||||
1. **Staging Deployment:**
|
||||
- Go to Actions → Deploy to Staging
|
||||
- Select "staging" environment
|
||||
- Click "Run workflow"
|
||||
|
||||
2. **Development Testing:**
|
||||
- Select "development" environment
|
||||
- Useful for quick testing without affecting staging
|
||||
|
||||
3. **Preview Deployment:**
|
||||
- Select "preview" environment
|
||||
- For showcasing features before staging
|
||||
|
||||
### Post-Deployment Steps
|
||||
|
||||
After deployment to Joomla:
|
||||
|
||||
1. Log in to Joomla administrator
|
||||
2. Go to System → Extensions → Discover
|
||||
3. Click "Discover" to find the template
|
||||
4. Click "Install" to complete installation
|
||||
5. Go to System → Site Templates
|
||||
6. Configure template settings
|
||||
7. Set as default template if desired
|
||||
|
||||
## CI/CD Pipeline Details
|
||||
|
||||
### Build Process
|
||||
|
||||
1. **Validation** - All scripts validate before packaging
|
||||
2. **Packaging** - Create ZIP with proper structure
|
||||
3. **Testing** - Run on multiple PHP/Joomla versions
|
||||
4. **Quality** - PHPStan and PHPCS analysis
|
||||
5. **Deployment** - SFTP upload to target environment
|
||||
|
||||
### Matrix Testing Strategy
|
||||
|
||||
- **PHP Versions:** 8.0, 8.1, 8.2, 8.3
|
||||
- **Joomla Versions:** 4.4 LTS, 5.0, 5.1
|
||||
- **Exclusions:** PHP 8.3 not tested with Joomla 4.4 (incompatible)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Issue: PHP_CodeSniffer not found**
|
||||
```bash
|
||||
composer global require "squizlabs/php_codesniffer:^3.0"
|
||||
export PATH="$PATH:$HOME/.composer/vendor/bin"
|
||||
```
|
||||
|
||||
**Issue: PHPStan errors**
|
||||
```bash
|
||||
# Increase analysis memory
|
||||
php -d memory_limit=1G $(which phpstan) analyse
|
||||
```
|
||||
|
||||
**Issue: Joomla installation fails in CI**
|
||||
- Check MySQL service is running
|
||||
- Verify database credentials
|
||||
- Ensure PHP extensions are installed
|
||||
|
||||
**Issue: SFTP deployment fails**
|
||||
- Verify SSH key is correctly formatted
|
||||
- Check firewall allows port 22
|
||||
- Ensure STAGING_PATH exists on server
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new workflows or scripts:
|
||||
|
||||
1. Follow existing script structure
|
||||
2. Add proper error handling
|
||||
3. Include usage documentation
|
||||
4. Test with multiple PHP versions
|
||||
5. Update this documentation
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- Open an issue on GitHub
|
||||
- Check existing workflow runs for examples
|
||||
- Review test output in Actions tab
|
||||
|
||||
## License
|
||||
|
||||
All scripts and workflows are licensed under GPL-3.0-or-later, same as the main project.
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* Document: docs/JOOMLA_DEVELOPMENT.md
|
||||
* Repository: [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* Path: /docs/JOOMLA_DEVELOPMENT.md
|
||||
* Owner: Moko Consulting
|
||||
* Version: 03.06.03
|
||||
* Status: Active
|
||||
* Effective Date: 2026-01-30
|
||||
* Classification: Public Open Source Documentation
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ----------------------------------------------------- | --------------- |
|
||||
| 2026-01-30 | Updated metadata to MokoStandards format | GitHub Copilot |
|
||||
| 2025-01-04 | Initial Joomla development guide created | GitHub Copilot |
|
||||
310
docs/MANUAL_DEPLOYMENT.md
Normal file
310
docs/MANUAL_DEPLOYMENT.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# Manual Deployment Guide - MokoOnyx
|
||||
|
||||
This guide explains how to manually deploy the MokoOnyx template from the `src` directory to a Joomla installation without using the build/packaging process.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Understanding the Structure](#understanding-the-structure)
|
||||
- [Manual Deployment Methods](#manual-deployment-methods)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [When to Use Manual Deployment](#when-to-use-manual-deployment)
|
||||
|
||||
## Overview
|
||||
|
||||
**Important**: The `src` directory in this repository is the development source, not a ready-to-install package. For production use, we recommend using the packaged ZIP file from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases).
|
||||
|
||||
However, for development or testing purposes, you can manually deploy files from the `src` directory to your Joomla installation.
|
||||
|
||||
## Understanding the Structure
|
||||
|
||||
### Repository Structure
|
||||
|
||||
The `src/` directory contains:
|
||||
|
||||
```
|
||||
src/
|
||||
├── component.php # Template file
|
||||
├── error.php # Template file
|
||||
├── index.php # Main template file
|
||||
├── offline.php # Template file
|
||||
├── templateDetails.xml # Template manifest
|
||||
├── joomla.asset.json # Asset registration
|
||||
├── html/ # Module & component overrides
|
||||
├── language/ # Frontend language files
|
||||
├── administrator/ # Backend language files
|
||||
│ └── language/
|
||||
└── media/ # Assets (CSS, JS, images, fonts)
|
||||
├── css/
|
||||
├── js/
|
||||
├── images/
|
||||
└── fonts/
|
||||
```
|
||||
|
||||
### Joomla Installation Structure
|
||||
|
||||
Joomla expects template files in these locations:
|
||||
|
||||
```
|
||||
YOUR_JOOMLA_ROOT/
|
||||
├── templates/
|
||||
│ └── mokoonyx/ # Template files go here
|
||||
│ ├── component.php
|
||||
│ ├── error.php
|
||||
│ ├── index.php
|
||||
│ ├── offline.php
|
||||
│ ├── templateDetails.xml
|
||||
│ ├── joomla.asset.json
|
||||
│ ├── html/
|
||||
│ ├── language/
|
||||
│ └── administrator/
|
||||
└── media/
|
||||
└── templates/
|
||||
└── site/
|
||||
└── mokoonyx/ # Media files go here
|
||||
├── css/
|
||||
├── js/
|
||||
├── images/
|
||||
└── fonts/
|
||||
```
|
||||
|
||||
**Key Point**: Template files and media files go to **different locations** in Joomla!
|
||||
|
||||
## Manual Deployment Methods
|
||||
|
||||
### Method 1: Recommended - Upload as ZIP (Still Manual)
|
||||
|
||||
This method mimics what Joomla's installer does automatically.
|
||||
|
||||
1. **Prepare the template directory**:
|
||||
```bash
|
||||
# From the repository root
|
||||
cd src
|
||||
|
||||
# Copy all files EXCEPT media to a temp directory
|
||||
mkdir -p /tmp/mokoonyx
|
||||
cp component.php /tmp/mokoonyx/
|
||||
cp error.php /tmp/mokoonyx/
|
||||
cp index.php /tmp/mokoonyx/
|
||||
cp offline.php /tmp/mokoonyx/
|
||||
cp templateDetails.xml /tmp/mokoonyx/
|
||||
cp joomla.asset.json /tmp/mokoonyx/
|
||||
cp -r html /tmp/mokoonyx/
|
||||
cp -r language /tmp/mokoonyx/
|
||||
cp -r administrator /tmp/mokoonyx/
|
||||
|
||||
# Copy media to a separate temp directory
|
||||
mkdir -p /tmp/mokoonyx_media
|
||||
cp -r media/* /tmp/mokoonyx_media/
|
||||
```
|
||||
|
||||
2. **Upload to Joomla via FTP/SFTP**:
|
||||
```bash
|
||||
# Upload template files
|
||||
# Replace with your actual Joomla path
|
||||
scp -r /tmp/mokoonyx/* user@yourserver:/path/to/joomla/templates/mokoonyx/
|
||||
|
||||
# Upload media files
|
||||
scp -r /tmp/mokoonyx_media/* user@yourserver:/path/to/joomla/media/templates/site/mokoonyx/
|
||||
```
|
||||
|
||||
3. **Set proper permissions**:
|
||||
```bash
|
||||
# On your server
|
||||
cd /path/to/joomla
|
||||
chmod 755 templates/mokoonyx
|
||||
chmod 644 templates/mokoonyx/*
|
||||
chmod 755 templates/mokoonyx/html
|
||||
chmod 755 media/templates/site/mokoonyx
|
||||
```
|
||||
|
||||
### Method 2: Direct Copy to Existing Installation
|
||||
|
||||
If you have direct filesystem access (e.g., local development):
|
||||
|
||||
1. **Copy template files** (excluding media):
|
||||
```bash
|
||||
# From repository root
|
||||
cd src
|
||||
|
||||
# Copy to Joomla templates directory
|
||||
cp component.php /path/to/joomla/templates/mokoonyx/
|
||||
cp error.php /path/to/joomla/templates/mokoonyx/
|
||||
cp index.php /path/to/joomla/templates/mokoonyx/
|
||||
cp offline.php /path/to/joomla/templates/mokoonyx/
|
||||
cp templateDetails.xml /path/to/joomla/templates/mokoonyx/
|
||||
cp joomla.asset.json /path/to/joomla/templates/mokoonyx/
|
||||
|
||||
# Copy directories
|
||||
cp -r html /path/to/joomla/templates/mokoonyx/
|
||||
cp -r language /path/to/joomla/templates/mokoonyx/
|
||||
cp -r administrator /path/to/joomla/templates/mokoonyx/
|
||||
```
|
||||
|
||||
2. **Copy media files separately**:
|
||||
```bash
|
||||
# Copy media to the media directory
|
||||
cp -r media/* /path/to/joomla/media/templates/site/mokoonyx/
|
||||
```
|
||||
|
||||
3. **Clear Joomla cache**:
|
||||
- In Joomla admin: **System → Clear Cache**
|
||||
- Or delete: `/path/to/joomla/cache/*` and `/path/to/joomla/administrator/cache/*`
|
||||
|
||||
### Method 3: Symlink for Development (Linux/Mac only)
|
||||
|
||||
For active development where you want changes to immediately reflect:
|
||||
|
||||
1. **Create symlinks**:
|
||||
```bash
|
||||
# Remove existing directory if present
|
||||
rm -rf /path/to/joomla/templates/mokoonyx
|
||||
rm -rf /path/to/joomla/media/templates/site/mokoonyx
|
||||
|
||||
# Create parent directories if needed
|
||||
mkdir -p /path/to/joomla/templates
|
||||
mkdir -p /path/to/joomla/media/templates/site
|
||||
|
||||
# Symlink template files
|
||||
ln -s /path/to/MokoOnyx/src /path/to/joomla/templates/mokoonyx
|
||||
|
||||
# Symlink media files
|
||||
ln -s /path/to/MokoOnyx/src/media /path/to/joomla/media/templates/site/mokoonyx
|
||||
```
|
||||
|
||||
2. **Note**: This won't work as-is because the src directory includes the media folder. You'll need to:
|
||||
```bash
|
||||
# Better approach for symlinks:
|
||||
# Link everything except media at template root
|
||||
cd /path/to/joomla/templates
|
||||
mkdir -p mokoonyx
|
||||
cd mokoonyx
|
||||
|
||||
ln -s /path/to/MokoOnyx/src/component.php
|
||||
ln -s /path/to/MokoOnyx/src/error.php
|
||||
ln -s /path/to/MokoOnyx/src/index.php
|
||||
ln -s /path/to/MokoOnyx/src/offline.php
|
||||
ln -s /path/to/MokoOnyx/src/templateDetails.xml
|
||||
ln -s /path/to/MokoOnyx/src/joomla.asset.json
|
||||
ln -s /path/to/MokoOnyx/src/html
|
||||
ln -s /path/to/MokoOnyx/src/language
|
||||
ln -s /path/to/MokoOnyx/src/administrator
|
||||
|
||||
# Link media separately
|
||||
ln -s /path/to/MokoOnyx/src/media /path/to/joomla/media/templates/site/mokoonyx
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Language Files Not Loading
|
||||
|
||||
**Problem**: Language strings appear as language keys (e.g., `TPL_MOKOONYX_LABEL`)
|
||||
|
||||
**Solution**: Ensure the `language` and `administrator` folders are present in your template directory:
|
||||
|
||||
```bash
|
||||
# Check if folders exist
|
||||
ls -la /path/to/joomla/templates/mokoonyx/language
|
||||
ls -la /path/to/joomla/templates/mokoonyx/administrator
|
||||
```
|
||||
|
||||
The `templateDetails.xml` should contain (lines 54-55):
|
||||
```xml
|
||||
<files>
|
||||
<!-- ... other files ... -->
|
||||
<folder>language</folder>
|
||||
<folder>administrator</folder>
|
||||
</files>
|
||||
```
|
||||
|
||||
### CSS/JS Not Loading
|
||||
|
||||
**Problem**: Styles or scripts don't apply
|
||||
|
||||
**Solution**: Verify media files are in the correct location:
|
||||
|
||||
```bash
|
||||
# Check media directory structure
|
||||
ls -la /path/to/joomla/media/templates/site/mokoonyx/
|
||||
# Should show: css/, js/, images/, fonts/
|
||||
```
|
||||
|
||||
Clear Joomla cache:
|
||||
- Admin: **System → Clear Cache**
|
||||
- Check browser developer console for 404 errors
|
||||
|
||||
### Template Not Appearing in Template Manager
|
||||
|
||||
**Problem**: MokoOnyx doesn't show in **System → Site Templates**
|
||||
|
||||
**Solution**:
|
||||
1. Verify `templateDetails.xml` is present in `/path/to/joomla/templates/mokoonyx/`
|
||||
2. Check file permissions (should be readable by web server)
|
||||
3. Verify XML is well-formed:
|
||||
```bash
|
||||
xmllint --noout /path/to/joomla/templates/mokoonyx/templateDetails.xml
|
||||
```
|
||||
4. Check Joomla's error logs for XML parsing errors
|
||||
|
||||
### File Permission Issues
|
||||
|
||||
**Problem**: "Permission denied" or template files not readable
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Set proper ownership (replace www-data with your web server user)
|
||||
chown -R www-data:www-data /path/to/joomla/templates/mokoonyx
|
||||
chown -R www-data:www-data /path/to/joomla/media/templates/site/mokoonyx
|
||||
|
||||
# Set proper permissions
|
||||
find /path/to/joomla/templates/mokoonyx -type d -exec chmod 755 {} \;
|
||||
find /path/to/joomla/templates/mokoonyx -type f -exec chmod 644 {} \;
|
||||
find /path/to/joomla/media/templates/site/mokoonyx -type d -exec chmod 755 {} \;
|
||||
find /path/to/joomla/media/templates/site/mokoonyx -type f -exec chmod 644 {} \;
|
||||
```
|
||||
|
||||
## When to Use Manual Deployment
|
||||
|
||||
### ✅ Use Manual Deployment For:
|
||||
|
||||
- **Active Development**: Testing changes immediately without rebuilding packages
|
||||
- **Local Development**: Working on a local Joomla instance
|
||||
- **Quick Fixes**: Making emergency hotfixes directly on a development server
|
||||
- **Learning**: Understanding the template structure and Joomla's file organization
|
||||
|
||||
### ❌ Don't Use Manual Deployment For:
|
||||
|
||||
- **Production Sites**: Always use packaged ZIP files from releases
|
||||
- **Client Sites**: Use proper Joomla extension installation
|
||||
- **Version Control**: Can lead to inconsistent deployments
|
||||
- **Staging Environments**: Use CI/CD or release packages
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always Test Locally First**: Don't deploy untested changes to production
|
||||
2. **Keep Backups**: Back up both template and media directories before updating
|
||||
3. **Use Version Control**: Track your customizations separately from manual deployments
|
||||
4. **Document Changes**: Note any manual file modifications
|
||||
5. **Clear Cache**: Always clear Joomla cache after manual file updates
|
||||
6. **Verify Permissions**: Ensure web server can read all files
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **[Quick Start Guide](QUICK_START.md)** - Development environment setup
|
||||
- **[Joomla Development Guide](JOOMLA_DEVELOPMENT.md)** - Complete development workflows
|
||||
- **[Release Process](RELEASE_PROCESS.md)** - How to create proper release packages
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter issues with manual deployment:
|
||||
|
||||
1. Check this troubleshooting guide first
|
||||
2. Review [Joomla's template documentation](https://docs.joomla.org/J4.x:Creating_a_Simple_Template)
|
||||
3. Open an issue on [GitHub](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||
4. Contact: hello@mokoconsulting.tech
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0.0
|
||||
**Last Updated**: 2026-03-01
|
||||
**Status**: Active
|
||||
725
docs/MODULE_OVERRIDES.md
Normal file
725
docs/MODULE_OVERRIDES.md
Normal file
@@ -0,0 +1,725 @@
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoOnyx.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: docs/MODULE_OVERRIDES.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Comprehensive guide to MokoOnyx mobile-responsive module overrides
|
||||
PATH: /docs/MODULE_OVERRIDES.md
|
||||
-->
|
||||
|
||||
# Module & Component Overrides — MokoOnyx
|
||||
|
||||
This document provides a comprehensive guide to all mobile-responsive module and component overrides included in MokoOnyx.
|
||||
|
||||
## Overview
|
||||
|
||||
MokoOnyx includes **16 mobile-responsive module overrides** and **12 component view overrides** designed to enhance the mobile user experience for third-party extensions and the Main Menu navigation.
|
||||
|
||||
**Important**: Following Cassiopeia template best practices, MokoOnyx avoids overriding standard Joomla core modules (such as mod_search, mod_login, mod_breadcrumbs) to ensure proper language loading and compatibility. **Exception**: mod_menu "Main Menu" override provides essential Bootstrap 5 collapsible dropdown functionality.
|
||||
|
||||
### Alternative Layouts, Not Replacements
|
||||
|
||||
**All MokoOnyx overrides use alternative layout names (`mobile.php`) instead of replacing default layouts (`default.php`).** This means:
|
||||
|
||||
- ✅ Default Joomla layouts continue to work unchanged
|
||||
- ✅ You must explicitly select the "mobile" layout in module/menu item settings
|
||||
- ✅ Joomla core updates don't break your site
|
||||
- ✅ Full control over which modules use enhanced layouts
|
||||
|
||||
**📖 See [OVERRIDE_PHILOSOPHY.md](OVERRIDE_PHILOSOPHY.md) for complete details on how to activate and use these alternative layouts.**
|
||||
|
||||
### Key Features
|
||||
|
||||
All module overrides share these characteristics:
|
||||
|
||||
- **Mobile-First Design**: Optimized for mobile devices with responsive breakpoints
|
||||
- **Touch Targets**: 48px on mobile, 44px on desktop (WCAG 2.1 compliant)
|
||||
- **Input Font Size**: 16px minimum on mobile (prevents iOS zoom)
|
||||
- **Accessibility**: Full ARIA labels, keyboard navigation, semantic HTML
|
||||
- **BEM Naming**: Consistent CSS class naming convention
|
||||
- **CSS Variables**: Integration with template color schemes
|
||||
- **Security**: Proper escaping, _JEXEC checks, index.html protection
|
||||
- **Documentation**: Each override includes comprehensive README
|
||||
|
||||
## Module Categories
|
||||
|
||||
### 1. VirtueMart E-Commerce Modules
|
||||
|
||||
Five comprehensive overrides for VirtueMart shopping functionality.
|
||||
|
||||
**Master Documentation**: [VIRTUEMART_MODULES_README.md](../src/html/VIRTUEMART_MODULES_README.md)
|
||||
|
||||
#### mod_virtuemart_cart
|
||||
**Location**: `src/html/mod_virtuemart_cart/`
|
||||
|
||||
Shopping cart display with product list and checkout button.
|
||||
|
||||
**Features**:
|
||||
- Responsive product cards
|
||||
- Remove item buttons with confirmations
|
||||
- Price display with currency formatting
|
||||
- Checkout button with prominent styling
|
||||
|
||||
#### mod_virtuemart_product
|
||||
**Location**: `src/html/mod_virtuemart_product/`
|
||||
|
||||
Product showcase with grid layouts.
|
||||
|
||||
**Features**:
|
||||
- Responsive grid: 1-4 columns based on screen size
|
||||
- Product images with hover effects
|
||||
- Price display and "Add to Cart" buttons
|
||||
- Rating display support
|
||||
|
||||
#### mod_virtuemart_currencies
|
||||
**Location**: `src/html/mod_virtuemart_currencies/`
|
||||
|
||||
Currency selector dropdown for multi-currency stores.
|
||||
|
||||
**Features**:
|
||||
- Accessible dropdown with proper labels
|
||||
- Currency symbol and name display
|
||||
- Responsive button styling
|
||||
|
||||
#### mod_virtuemart_category
|
||||
**Location**: `src/html/mod_virtuemart_category/`
|
||||
|
||||
Category navigation with hierarchical display.
|
||||
|
||||
**Features**:
|
||||
- Expandable subcategories
|
||||
- Product count display
|
||||
- Hierarchical indentation
|
||||
- Active category highlighting
|
||||
|
||||
#### mod_virtuemart_manufacturer
|
||||
**Location**: `src/html/mod_virtuemart_manufacturer/`
|
||||
|
||||
Manufacturer/brand display with grid layout.
|
||||
|
||||
**Features**:
|
||||
- Responsive grid: 2-4 columns
|
||||
- Logo display support
|
||||
- Product count per manufacturer
|
||||
|
||||
---
|
||||
|
||||
### 2. Main Menu & Community Builder Modules
|
||||
|
||||
Three essential Community Builder and navigation module overrides.
|
||||
|
||||
#### mod_menu (Main Menu)
|
||||
**Location**: `src/html/mod_menu/`
|
||||
|
||||
Bootstrap 5 responsive navigation menu with collapsible dropdown functionality.
|
||||
|
||||
**Files**:
|
||||
- `mainmenu.php` - Main layout with Bootstrap navbar
|
||||
- `mainmenu_component.php` - Component menu items
|
||||
- `mainmenu_heading.php` - Heading menu items
|
||||
- `mainmenu_separator.php` - Separator menu items
|
||||
- `mainmenu_url.php` - URL menu items
|
||||
|
||||
**Features**:
|
||||
- Bootstrap 5 navbar structure with collapsible hamburger menu
|
||||
- Multi-level dropdown support (hover on desktop, tap on mobile)
|
||||
- WCAG 2.1 compliant touch targets (48px mobile, 44px desktop)
|
||||
- BEM naming convention: `.mod-menu-main__*`
|
||||
- Active state indicators for current menu items
|
||||
- ARIA labels and keyboard navigation support
|
||||
- Alternative layout named `mainmenu.php` (not `default.php`)
|
||||
|
||||
**Activation**: Select "Mainmenu" layout in Joomla Administrator → Modules → Menu Module → Advanced Tab → Alternative Layout
|
||||
|
||||
**Note**: Unlike the broken mod_menu override removed in v03.08.01, this v03.08.03 version is properly structured based on Joomla core layouts and Bootstrap 5, ensuring language strings load correctly and menu functionality works as expected.
|
||||
|
||||
#### mod_cblogin
|
||||
**Location**: `src/html/mod_cblogin/`
|
||||
|
||||
Community Builder login with avatar display.
|
||||
|
||||
**Features**:
|
||||
- User avatar when logged in
|
||||
- CB-specific login form
|
||||
- Profile link
|
||||
- Logout button
|
||||
|
||||
#### mod_comprofilerOnline
|
||||
**Location**: `src/html/mod_comprofilerOnline/`
|
||||
|
||||
Community Builder online users display.
|
||||
|
||||
**Features**:
|
||||
- User count display
|
||||
- Avatar grid layout
|
||||
- Username display
|
||||
- Online status indicators
|
||||
|
||||
---
|
||||
|
||||
### 3. Industry Extension Modules
|
||||
|
||||
Eight popular third-party extension module overrides plus component views.
|
||||
|
||||
#### K2 Content Extension
|
||||
|
||||
##### mod_k2_content
|
||||
**Location**: `src/html/mod_k2_content/`
|
||||
|
||||
K2 content display with advanced layouts.
|
||||
|
||||
**Features**:
|
||||
- Responsive grid: 1-3 columns
|
||||
- Featured images with lazy loading
|
||||
- Category, author, date metadata
|
||||
- Excerpt support
|
||||
- Tag display
|
||||
|
||||
#### AcyMailing Newsletter
|
||||
|
||||
##### mod_acymailing
|
||||
**Location**: `src/html/mod_acymailing/`
|
||||
|
||||
Newsletter subscription form.
|
||||
|
||||
**Features**:
|
||||
- Email validation
|
||||
- Privacy checkbox
|
||||
- Success/error messaging
|
||||
- GDPR compliance fields
|
||||
|
||||
#### HikaShop E-Commerce
|
||||
|
||||
##### mod_hikashop_cart
|
||||
**Location**: `src/html/mod_hikashop_cart/`
|
||||
|
||||
HikaShop shopping cart module.
|
||||
|
||||
**Features**:
|
||||
- Product list with images
|
||||
- Quantity adjustment
|
||||
- Price totals
|
||||
- Checkout button
|
||||
|
||||
#### Kunena Forum
|
||||
|
||||
Four comprehensive forum modules plus component view.
|
||||
|
||||
##### mod_kunenalatest
|
||||
**Location**: `src/html/mod_kunenalatest/`
|
||||
|
||||
Latest forum posts display.
|
||||
|
||||
**Features**:
|
||||
- Post excerpts
|
||||
- Author avatars
|
||||
- Reply count
|
||||
- Post date
|
||||
|
||||
##### mod_kunenalogin
|
||||
**Location**: `src/html/mod_kunenalogin/`
|
||||
|
||||
Forum-specific login module.
|
||||
|
||||
**Features**:
|
||||
- User avatar display
|
||||
- Forum statistics
|
||||
- Quick login form
|
||||
- Profile link
|
||||
|
||||
##### mod_kunenasearch
|
||||
**Location**: `src/html/mod_kunenasearch/`
|
||||
|
||||
Forum search with button positions.
|
||||
|
||||
**Features**:
|
||||
- Multiple button positions (left, right, top)
|
||||
- Search placeholder text
|
||||
- Icon support
|
||||
- 48px touch targets
|
||||
|
||||
##### mod_kunenastats
|
||||
**Location**: `src/html/mod_kunenastats/`
|
||||
|
||||
Forum statistics display.
|
||||
|
||||
**Features**:
|
||||
- Visual stat cards
|
||||
- Member count
|
||||
- Topic/post totals
|
||||
- Latest member
|
||||
- Responsive grid layout
|
||||
|
||||
##### com_kunena (Component)
|
||||
**Location**: `src/html/com_kunena/`
|
||||
|
||||
Forum category list view.
|
||||
|
||||
**Views**:
|
||||
- `category/default.php` - Category listing with icons
|
||||
|
||||
#### OS Membership Pro
|
||||
|
||||
Module and component overrides for membership management.
|
||||
|
||||
##### mod_osmembership
|
||||
**Location**: `src/html/mod_osmembership/`
|
||||
|
||||
Membership plans module.
|
||||
|
||||
**Features**:
|
||||
- Plan cards with pricing
|
||||
- Feature lists
|
||||
- Signup buttons
|
||||
- Badge displays (popular, featured)
|
||||
|
||||
##### com_osmembership (Component)
|
||||
**Location**: `src/html/com_osmembership/`
|
||||
|
||||
Membership pricing tables.
|
||||
|
||||
**Views**:
|
||||
- `plans/default.php` - Responsive pricing table with comparison features
|
||||
|
||||
---
|
||||
|
||||
### 4. Community Builder Components
|
||||
|
||||
Four comprehensive component view overrides for Community Builder user management.
|
||||
|
||||
#### com_comprofiler
|
||||
**Location**: `src/html/com_comprofiler/`
|
||||
|
||||
Mobile-responsive views for Community Builder user profiles, registration, and login.
|
||||
|
||||
##### userprofile
|
||||
User profile display with tabbed interface.
|
||||
|
||||
**Features**:
|
||||
- Large avatar display (150px)
|
||||
- Tabbed interface for profile sections
|
||||
- Custom field display with labels
|
||||
- Online status indicator
|
||||
- Responsive layout: vertical mobile → horizontal desktop
|
||||
|
||||
##### userslist
|
||||
User directory with search and grid layout.
|
||||
|
||||
**Features**:
|
||||
- Search functionality with accessible form
|
||||
- Responsive grid: 1 column mobile → 2-3 columns desktop
|
||||
- User cards with avatars (80px)
|
||||
- Custom field display
|
||||
- Profile view buttons
|
||||
- Pagination support
|
||||
|
||||
##### registers
|
||||
Multi-step registration form with validation.
|
||||
|
||||
**Features**:
|
||||
- Fieldset organization with legends
|
||||
- Required field indicators (*)
|
||||
- Input validation and error display
|
||||
- Captcha support section
|
||||
- Terms & conditions checkbox
|
||||
- GDPR-compliant design
|
||||
- 16px input font on mobile
|
||||
|
||||
##### login
|
||||
Login page with remember me and helper links.
|
||||
|
||||
**Features**:
|
||||
- Centered login container (max-width: 450px)
|
||||
- Username/password fields with autocomplete
|
||||
- Remember me checkbox
|
||||
- Registration and password recovery links
|
||||
- CSRF token support
|
||||
- Responsive padding adjustments
|
||||
|
||||
### 5. JEM (Joomla Event Manager) Components
|
||||
|
||||
Five comprehensive component view overrides for JEM event management.
|
||||
|
||||
#### com_jem
|
||||
**Location**: `src/html/com_jem/`
|
||||
|
||||
Mobile-responsive views for JEM event listings, details, calendar, venues, and categories.
|
||||
|
||||
##### eventslist
|
||||
Event listing with card-based layout.
|
||||
|
||||
**Features**:
|
||||
- Event cards with date, time, and venue
|
||||
- Category badges with color coding
|
||||
- Responsive event grid layout
|
||||
- Event description excerpts
|
||||
- Read more buttons with clear calls-to-action
|
||||
- Pagination support
|
||||
- Empty state messaging
|
||||
|
||||
##### event
|
||||
Single event details view with comprehensive information.
|
||||
|
||||
**Features**:
|
||||
- Large event image display (responsive)
|
||||
- Date and time with structured data
|
||||
- Venue information with maps link
|
||||
- Event description with full content
|
||||
- Category display with badges
|
||||
- Registration information (if enabled)
|
||||
- Contact information display
|
||||
- Back to events navigation
|
||||
- Meta information with icons
|
||||
|
||||
##### calendar
|
||||
Monthly calendar view with event indicators.
|
||||
|
||||
**Features**:
|
||||
- Month navigation (previous/next)
|
||||
- Calendar grid with weekday headers
|
||||
- Event indicators on dates with events
|
||||
- Responsive calendar layout
|
||||
- Today highlighting
|
||||
- Event list for selected month
|
||||
- Event count per day display
|
||||
- Touch-friendly navigation buttons
|
||||
|
||||
##### venue
|
||||
Venue details with location and upcoming events.
|
||||
|
||||
**Features**:
|
||||
- Venue image display
|
||||
- Complete address information
|
||||
- Website link (external)
|
||||
- Google Maps integration
|
||||
- Venue description
|
||||
- Upcoming events at venue
|
||||
- Location coordinates display
|
||||
- Back navigation
|
||||
|
||||
##### categories
|
||||
Event category listing with descriptions.
|
||||
|
||||
**Features**:
|
||||
- Category cards with images
|
||||
- Category descriptions
|
||||
- Event count per category
|
||||
- View category buttons
|
||||
- Responsive grid layout
|
||||
- Empty state messaging
|
||||
- Pagination support
|
||||
|
||||
---
|
||||
|
||||
## CSS Architecture
|
||||
|
||||
All module styles are located in `src/media/css/template.css` with dedicated sections:
|
||||
|
||||
### CSS Sections
|
||||
|
||||
1. **MOD_SEARCH MOBILE RESPONSIVE STYLES** (Lines ~18400+)
|
||||
- Search box layouts
|
||||
- Button position variants
|
||||
- Input styling
|
||||
|
||||
2. **VIRTUEMART MODULE MOBILE RESPONSIVE STYLES** (Lines ~18500+)
|
||||
- Cart product cards
|
||||
- Product grids
|
||||
- Currency selector
|
||||
- Category navigation
|
||||
- Manufacturer displays
|
||||
|
||||
3. **STANDARD JOOMLA & COMMUNITY BUILDER MODULE STYLES** (Lines ~19300+)
|
||||
- Menu navigation
|
||||
- Breadcrumbs
|
||||
- Login forms
|
||||
- Article displays
|
||||
- CB module components
|
||||
|
||||
4. **INDUSTRY EXTENSION MODULE STYLES** (Lines ~19800+)
|
||||
- K2 content grids
|
||||
- AcyMailing forms
|
||||
- HikaShop cart
|
||||
- Kunena forum modules
|
||||
- OS Membership pricing
|
||||
|
||||
5. **COMMUNITY BUILDER COMPONENT STYLES** (Lines ~21000+)
|
||||
- User profile layouts
|
||||
- Users list grids
|
||||
- Registration forms
|
||||
- Login pages
|
||||
- Tab interfaces
|
||||
|
||||
6. **JEM COMPONENT STYLES** (Lines ~22000+)
|
||||
- Event list cards
|
||||
- Event details layout
|
||||
- Calendar grid
|
||||
- Venue information
|
||||
- Category displays
|
||||
|
||||
### CSS Variables Integration
|
||||
|
||||
All modules integrate with template CSS variables:
|
||||
|
||||
```css
|
||||
/* Common Variables Used */
|
||||
--body-color /* Text color */
|
||||
--link-color /* Link color */
|
||||
--link-hover-color /* Link hover color */
|
||||
--border-color /* Border color */
|
||||
--secondary-bg /* Background color */
|
||||
--border-radius /* Border radius */
|
||||
--input-bg /* Input background */
|
||||
--input-border-color /* Input border */
|
||||
--btn-primary-bg /* Primary button */
|
||||
--btn-primary-hover-bg /* Button hover */
|
||||
```
|
||||
|
||||
See [CSS_VARIABLES.md](CSS_VARIABLES.md) for complete reference.
|
||||
|
||||
---
|
||||
|
||||
## Responsive Breakpoints
|
||||
|
||||
All modules use Bootstrap-aligned breakpoints:
|
||||
|
||||
| Breakpoint | Size | Typical Changes |
|
||||
|------------|-----------|-----------------------------------|
|
||||
| `xs` | < 576px | Single column, stacked layouts |
|
||||
| `sm` | ≥ 576px | 2 columns for grids |
|
||||
| `md` | ≥ 768px | 3 columns, horizontal layouts |
|
||||
| `lg` | ≥ 992px | 4 columns, expanded spacing |
|
||||
| `xl` | ≥ 1200px | Maximum width, optimal spacing |
|
||||
| `xxl` | ≥ 1400px | Extra spacing |
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Features
|
||||
|
||||
All overrides implement comprehensive accessibility:
|
||||
|
||||
### ARIA Labels
|
||||
- Descriptive labels for all interactive elements
|
||||
- `aria-label` for icon-only buttons
|
||||
- `aria-describedby` for form fields
|
||||
- `aria-live` for dynamic content
|
||||
|
||||
### Keyboard Navigation
|
||||
- Proper tab order
|
||||
- Focus states on all interactive elements
|
||||
- Keyboard-accessible dropdowns
|
||||
- Skip links where appropriate
|
||||
|
||||
### Screen Readers
|
||||
- Semantic HTML5 elements
|
||||
- Hidden text for icon-only elements
|
||||
- Proper heading hierarchy
|
||||
- Alternative text for images
|
||||
|
||||
### WCAG 2.1 Compliance
|
||||
- Touch targets: 48px minimum on mobile
|
||||
- Color contrast ratios meet AA standards
|
||||
- Text resizable to 200% without loss
|
||||
- No content relies on color alone
|
||||
|
||||
---
|
||||
|
||||
## Customization Guide
|
||||
|
||||
### Override Customization
|
||||
|
||||
Each module can be customized in two ways:
|
||||
|
||||
#### 1. CSS Customization
|
||||
|
||||
Edit `src/media/css/user.css` to add custom styles:
|
||||
|
||||
```css
|
||||
/* Example: Change product grid columns */
|
||||
@media (min-width: 768px) {
|
||||
.mod-vm-product__grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Example: Customize cart button */
|
||||
.mod-vm-cart__checkout-button {
|
||||
background-color: #28a745;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Template Override Customization
|
||||
|
||||
Copy the entire module directory and modify:
|
||||
|
||||
```bash
|
||||
# Keep original override as reference
|
||||
cp -r src/html/mod_virtuemart_cart src/html/mod_virtuemart_cart_original
|
||||
|
||||
# Modify your version
|
||||
# Edit src/html/mod_virtuemart_cart/default.php
|
||||
```
|
||||
|
||||
### CSS Variables Override
|
||||
|
||||
Override CSS variables in your custom color scheme:
|
||||
|
||||
```css
|
||||
/* src/media/css/theme/light.custom.css */
|
||||
:root {
|
||||
--vm-price-color: #28a745;
|
||||
--vm-cart-bg: #f8f9fa;
|
||||
--vm-button-primary: #007bff;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When Using Overrides
|
||||
|
||||
1. **Test Across Devices**: Always test on actual mobile devices
|
||||
2. **Maintain Accessibility**: Don't remove ARIA labels or keyboard navigation
|
||||
3. **Keep BEM Naming**: Use established class naming patterns
|
||||
4. **Security First**: Always escape output and validate input
|
||||
5. **Document Changes**: Comment your customizations
|
||||
|
||||
### When Updating
|
||||
|
||||
1. **Backup First**: Always backup your site before updating
|
||||
2. **Review Changes**: Check CHANGELOG.md for breaking changes
|
||||
3. **Test Thoroughly**: Test all modules after updates
|
||||
4. **Custom Overrides**: May need adjustments after template updates
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Module Not Displaying Correctly
|
||||
1. Clear Joomla cache (System → Clear Cache)
|
||||
2. Check module is published and assigned to correct position
|
||||
3. Verify template is assigned to menu items
|
||||
4. Check browser console for JavaScript errors
|
||||
|
||||
#### Styles Not Applying
|
||||
1. Clear browser cache (Ctrl+F5 / Cmd+Shift+R)
|
||||
2. Verify `template.css` is loading
|
||||
3. Check CSS specificity conflicts
|
||||
4. Review custom CSS in `user.css`
|
||||
|
||||
#### Mobile View Issues
|
||||
1. Test with browser dev tools responsive mode
|
||||
2. Check viewport meta tag in template
|
||||
3. Verify breakpoint media queries
|
||||
4. Test on actual devices when possible
|
||||
|
||||
#### Accessibility Issues
|
||||
1. Run WAVE or axe DevTools accessibility check
|
||||
2. Test with keyboard navigation only
|
||||
3. Verify screen reader compatibility
|
||||
4. Check color contrast ratios
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Documentation**: Check module-specific README files
|
||||
- **GitHub Issues**: [Report issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||
- **Support**: hello@mokoconsulting.tech
|
||||
|
||||
---
|
||||
|
||||
## How to Activate Alternative Layouts
|
||||
|
||||
All MokoOnyx overrides are **alternative layouts** that must be explicitly activated. They do not automatically replace default layouts.
|
||||
|
||||
### Quick Start: Enable Mobile Layout
|
||||
|
||||
1. **Go to Joomla Administrator** → Extensions → Modules
|
||||
2. **Open the module** you want to enhance (e.g., VirtueMart Cart)
|
||||
3. **Navigate to Advanced tab**
|
||||
4. **Find "Alternative Layout" field**
|
||||
5. **Select "MokoOnyx - mobile"** from dropdown
|
||||
6. **Save & Close**
|
||||
|
||||
### For Menu Items (Component Views)
|
||||
|
||||
1. **Go to Menus** → Select your menu
|
||||
2. **Open the menu item** (e.g., Events List)
|
||||
3. **Navigate to Advanced Options or Page Display tab**
|
||||
4. **Find "Alternative Layout" field**
|
||||
5. **Select "MokoOnyx - mobile"** from dropdown
|
||||
6. **Save & Close**
|
||||
|
||||
### Apply to All Modules in a Position
|
||||
|
||||
In your template's `index.php`, specify layout for entire module position:
|
||||
|
||||
```php
|
||||
<jdoc:include type="modules" name="sidebar-left" style="none" layout="mobile" />
|
||||
```
|
||||
|
||||
**📖 For complete documentation, see [OVERRIDE_PHILOSOPHY.md](OVERRIDE_PHILOSOPHY.md)**
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Changes |
|
||||
|----------|------------|--------------------------------------------------|
|
||||
| 03.08.04 | 2026-02-27 | Added alternative layout activation instructions, JEM overrides |
|
||||
| 03.08.03 | 2026-02-25 | Removed mod_search override per Cassiopeia philosophy |
|
||||
| 03.08.00 | 2026-02-22 | Added Community Builder component overrides |
|
||||
| 03.07.00 | 2026-02-22 | Initial release of all mobile-responsive overrides |
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Override Philosophy**: [OVERRIDE_PHILOSOPHY.md](OVERRIDE_PHILOSOPHY.md) ⭐ **Start here**
|
||||
- **Main README**: [README.md](../README.md)
|
||||
- **Changelog**: [CHANGELOG.md](../CHANGELOG.md)
|
||||
- **CSS Variables**: [CSS_VARIABLES.md](CSS_VARIABLES.md)
|
||||
- **Repository**: [GitHub](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* Document: docs/MODULE_OVERRIDES.md
|
||||
* Repository: [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* Path: /docs/MODULE_OVERRIDES.md
|
||||
* Owner: Moko Consulting
|
||||
* Version: 03.07.00
|
||||
* Status: Active
|
||||
* Effective Date: 2026-02-22
|
||||
* Classification: Public Open Source Documentation
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ----------------------------------------------------- | --------------- |
|
||||
| 2026-02-22 | Initial creation with comprehensive module override documentation | GitHub Copilot |
|
||||
332
docs/OVERRIDE_PHILOSOPHY.md
Normal file
332
docs/OVERRIDE_PHILOSOPHY.md
Normal file
@@ -0,0 +1,332 @@
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoOnyx.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: docs/OVERRIDE_PHILOSOPHY.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Philosophy and implementation of non-replacing alternative layouts
|
||||
PATH: /docs/OVERRIDE_PHILOSOPHY.md
|
||||
-->
|
||||
|
||||
# Override Philosophy — MokoOnyx
|
||||
|
||||
## Core Principle: Add-On, Not Replacement
|
||||
|
||||
**MokoOnyx overrides are designed as alternative layouts, not replacements of default Joomla layouts.**
|
||||
|
||||
This means:
|
||||
- ✅ Default Joomla layouts continue to work unchanged
|
||||
- ✅ Site administrators can choose when to use our enhanced layouts
|
||||
- ✅ Updates to Joomla core layouts don't break the site
|
||||
- ✅ Compatibility with other extensions is maintained
|
||||
- ✅ Users have control over which layouts to use
|
||||
|
||||
---
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Layout Naming Convention
|
||||
|
||||
All MokoOnyx overrides use **`mobile.php`** naming instead of **`default.php`**:
|
||||
|
||||
```
|
||||
❌ BAD (Replaces default):
|
||||
src/html/mod_virtuemart_cart/default.php
|
||||
|
||||
✅ GOOD (Alternative layout):
|
||||
src/html/mod_virtuemart_cart/mobile.php
|
||||
```
|
||||
|
||||
### How Joomla Handles Layouts
|
||||
|
||||
When a module or component looks for a layout, Joomla searches in this order:
|
||||
|
||||
1. **Template override with specified layout name**: `templates/mokoonyx/html/{extension}/{view}/{layout}.php`
|
||||
2. **Extension's specified layout**: `{extension}/tmpl/{view}/{layout}.php`
|
||||
3. **Template override for default layout**: `templates/mokoonyx/html/{extension}/{view}/default.php`
|
||||
4. **Extension's default layout**: `{extension}/tmpl/{view}/default.php`
|
||||
|
||||
By naming our overrides `mobile.php` instead of `default.php`, they become **step 1** alternatives that must be explicitly selected, rather than **step 3** replacements that are automatically used.
|
||||
|
||||
---
|
||||
|
||||
## How to Use Alternative Layouts
|
||||
|
||||
### Method 1: Module/Menu Item Settings
|
||||
|
||||
When editing a module or menu item in Joomla administrator:
|
||||
|
||||
1. Open the module/menu item for editing
|
||||
2. Navigate to the **Advanced** tab
|
||||
3. Find the **Alternative Layout** field
|
||||
4. Select **MokoOnyx - mobile** from the dropdown
|
||||
5. Save
|
||||
|
||||
### Method 2: Override in Module Position
|
||||
|
||||
If you want all modules in a specific position to use the mobile layout:
|
||||
|
||||
```php
|
||||
<!-- In your index.php template file -->
|
||||
<?php if ($this->countModules('sidebar-left')) : ?>
|
||||
<jdoc:include type="modules" name="sidebar-left" style="none" layout="mobile" />
|
||||
<?php endif; ?>
|
||||
```
|
||||
|
||||
### Method 3: Module Chrome (Advanced)
|
||||
|
||||
Create a custom module chrome in `templates/mokoonyx/html/layouts/chromes/` that automatically applies the mobile layout.
|
||||
|
||||
---
|
||||
|
||||
## Exception: Main Menu
|
||||
|
||||
**The only exception** to this philosophy is `mod_menu` with the "Main Menu" module type.
|
||||
|
||||
The template includes files like:
|
||||
- `src/html/mod_menu/mainmenu.php`
|
||||
- `src/html/mod_menu/mainmenu_component.php`
|
||||
- `src/html/mod_menu/mainmenu_heading.php`
|
||||
- `src/html/mod_menu/mainmenu_url.php`
|
||||
- `src/html/mod_menu/mainmenu_separator.php`
|
||||
|
||||
These use a **custom layout name** (`mainmenu`) instead of replacing `default.php`, which allows the site to:
|
||||
- Use the enhanced Bootstrap 5 collapsible menu for main navigation
|
||||
- Keep standard Joomla menus working in other positions
|
||||
- Provide better mobile navigation without breaking existing menus
|
||||
|
||||
To use this layout, set the module's **Alternative Layout** to **MokoOnyx - mainmenu**.
|
||||
|
||||
---
|
||||
|
||||
## Override Inventory
|
||||
|
||||
### Module Overrides (16 total)
|
||||
|
||||
All use `mobile.php` naming (alternative layout):
|
||||
|
||||
**VirtueMart (5)**:
|
||||
- `mod_virtuemart_cart/mobile.php`
|
||||
- `mod_virtuemart_product/mobile.php`
|
||||
- `mod_virtuemart_currencies/mobile.php`
|
||||
- `mod_virtuemart_category/mobile.php`
|
||||
- `mod_virtuemart_manufacturer/mobile.php`
|
||||
|
||||
**Community Builder (2)**:
|
||||
- `mod_cblogin/mobile.php`
|
||||
- `mod_comprofilerOnline/mobile.php`
|
||||
|
||||
**Main Menu (1)**:
|
||||
- `mod_menu/mainmenu.php` (custom layout name)
|
||||
|
||||
**Industry Extensions (8)**:
|
||||
- `mod_k2_content/mobile.php`
|
||||
- `mod_acymailing/mobile.php`
|
||||
- `mod_hikashop_cart/mobile.php`
|
||||
- `mod_kunenalatest/mobile.php`
|
||||
- `mod_kunenalogin/mobile.php`
|
||||
- `mod_kunenasearch/mobile.php`
|
||||
- `mod_kunenastats/mobile.php`
|
||||
- `mod_osmembership/mobile.php`
|
||||
|
||||
### Component View Overrides (12 total)
|
||||
|
||||
All use `mobile.php` naming (alternative layout):
|
||||
|
||||
**Community Builder (4)**:
|
||||
- `com_comprofiler/userprofile/mobile.php`
|
||||
- `com_comprofiler/userslist/mobile.php`
|
||||
- `com_comprofiler/registers/mobile.php`
|
||||
- `com_comprofiler/login/mobile.php`
|
||||
|
||||
**JEM - Joomla Event Manager (5)**:
|
||||
- `com_jem/eventslist/mobile.php`
|
||||
- `com_jem/event/mobile.php`
|
||||
- `com_jem/calendar/mobile.php`
|
||||
- `com_jem/venue/mobile.php`
|
||||
- `com_jem/categories/mobile.php`
|
||||
|
||||
**Kunena Forum (1)**:
|
||||
- `com_kunena/category/mobile.php`
|
||||
|
||||
**OSMembership (2)**:
|
||||
- `com_osmembership/plan/mobile.php`
|
||||
- `com_osmembership/plans/mobile.php`
|
||||
|
||||
**Joomla Core (2)**:
|
||||
- `com_content/article/toc-left.php` (custom layout name)
|
||||
- `com_content/article/toc-right.php` (custom layout name)
|
||||
|
||||
---
|
||||
|
||||
## Benefits of This Approach
|
||||
|
||||
### 1. **Zero Breaking Changes**
|
||||
|
||||
Existing sites continue to work exactly as before. No layouts are forcibly changed.
|
||||
|
||||
### 2. **Gradual Adoption**
|
||||
|
||||
Site administrators can:
|
||||
- Test mobile layouts on specific modules first
|
||||
- Roll out changes module-by-module
|
||||
- Keep some modules using default layouts if needed
|
||||
- Easily revert by changing the Alternative Layout setting
|
||||
|
||||
### 3. **Extension Compatibility**
|
||||
|
||||
Third-party extensions' default layouts remain untouched, preventing conflicts with:
|
||||
- Extension updates
|
||||
- Other templates
|
||||
- Custom development
|
||||
|
||||
### 4. **Joomla Core Updates**
|
||||
|
||||
When Joomla core updates:
|
||||
- Default layouts get new features/bug fixes automatically
|
||||
- Mobile layouts remain stable and tested
|
||||
- No emergency fixes needed after Joomla updates
|
||||
|
||||
### 5. **Multi-Language Support**
|
||||
|
||||
Joomla's language system loads extension language files properly because:
|
||||
- Extensions aren't hijacked by template overrides
|
||||
- Language strings come from the correct source
|
||||
- Translations work as expected
|
||||
|
||||
---
|
||||
|
||||
## Standards Not Overridden
|
||||
|
||||
Following Cassiopeia template best practices, MokoOnyx **does not override** standard Joomla core modules:
|
||||
|
||||
- ❌ `mod_breadcrumbs` - Use Joomla core layout
|
||||
- ❌ `mod_login` - Use Joomla core layout
|
||||
- ❌ `mod_articles_latest` - Use Joomla core layout
|
||||
- ❌ `mod_articles_category` - Use Joomla core layout
|
||||
- ❌ `mod_articles_news` - Use Joomla core layout
|
||||
- ❌ `mod_search` - Use Joomla core layout (removed in v03.08.03)
|
||||
|
||||
**Reason**: These modules have robust core layouts with proper language loading, accessibility, and ongoing Joomla maintenance.
|
||||
|
||||
---
|
||||
|
||||
## Developer Guidelines
|
||||
|
||||
When adding new overrides to MokoOnyx:
|
||||
|
||||
### ✅ DO:
|
||||
|
||||
1. Name files `mobile.php` or use descriptive custom names (`mainmenu.php`, `toc-left.php`)
|
||||
2. Document the alternative layout in MODULE_OVERRIDES.md
|
||||
3. Add CSS with BEM naming: `.{extension}-{view}__element`
|
||||
4. Test that default layouts still work
|
||||
5. Provide clear instructions for selecting the layout
|
||||
|
||||
### ❌ DON'T:
|
||||
|
||||
1. Create `default.php` files that replace core layouts
|
||||
2. Override standard Joomla core modules without strong justification
|
||||
3. Break backward compatibility
|
||||
4. Assume users will automatically get your layout
|
||||
5. Forget to document how to enable the alternative layout
|
||||
|
||||
---
|
||||
|
||||
## Migration from Replacing Overrides
|
||||
|
||||
If you're migrating from a template that used `default.php` overrides:
|
||||
|
||||
### Step 1: Identify Replaced Layouts
|
||||
|
||||
```bash
|
||||
find templates/oldtemplate/html -name "default.php"
|
||||
```
|
||||
|
||||
### Step 2: Rename to Alternative Layouts
|
||||
|
||||
```bash
|
||||
# For each default.php found:
|
||||
mv default.php mobile.php
|
||||
```
|
||||
|
||||
### Step 3: Update Module Settings
|
||||
|
||||
For each module using the old override:
|
||||
1. Edit module in administrator
|
||||
2. Advanced tab → Alternative Layout
|
||||
3. Select "mobile" from dropdown
|
||||
4. Save
|
||||
|
||||
### Step 4: Test
|
||||
|
||||
- Verify module displays correctly
|
||||
- Check that other modules still use default layouts
|
||||
- Confirm language strings load properly
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### My Alternative Layout Doesn't Appear in Dropdown
|
||||
|
||||
**Check:**
|
||||
1. File is in correct location: `templates/mokoonyx/html/{extension}/{view}/`
|
||||
2. File has `.php` extension
|
||||
3. File is not named `default.php`
|
||||
4. Cache is cleared (System → Clear Cache)
|
||||
|
||||
### Module Still Uses Default Layout
|
||||
|
||||
**Check:**
|
||||
1. Module's Alternative Layout setting in administrator
|
||||
2. Module position's `layout` parameter in `<jdoc:include>` tag
|
||||
3. File permissions (must be readable)
|
||||
4. Template is assigned to correct pages
|
||||
|
||||
### Layout Works But Looks Wrong
|
||||
|
||||
**Check:**
|
||||
1. CSS is loaded: inspect element and check for `.{extension}-{view}__` classes
|
||||
2. `template.css` is up to date
|
||||
3. Browser cache is cleared
|
||||
4. CSS variables are defined in template
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Joomla Docs: Layout Overrides](https://docs.joomla.org/Layout_Overrides_in_Joomla)
|
||||
- [Joomla Docs: Alternative Layouts](https://docs.joomla.org/J3.x:How_to_use_the_alternative_layout_feature)
|
||||
- [MODULE_OVERRIDES.md](MODULE_OVERRIDES.md) - Complete override inventory
|
||||
- [CSS_VARIABLES.md](CSS_VARIABLES.md) - Template styling system
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
- **03.08.04**: Created OVERRIDE_PHILOSOPHY.md document
|
||||
- **03.08.03**: Removed mod_search override to align with philosophy
|
||||
- **03.08.02**: Removed standard Joomla module overrides for proper language loading
|
||||
- **Earlier**: Renamed all overrides from default.php to mobile.php (21 files)
|
||||
333
docs/QUICK_START.md
Normal file
333
docs/QUICK_START.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# Quick Start Guide - MokoOnyx Development
|
||||
|
||||
Get up and running with MokoOnyx development in minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, ensure you have:
|
||||
|
||||
- **Git** - For version control
|
||||
- **PHP 8.0+** - Required runtime
|
||||
- **Composer** - PHP dependency manager
|
||||
- **Make** (optional) - For convenient commands
|
||||
- **Code Editor** - VS Code recommended (tasks pre-configured)
|
||||
|
||||
## 5-Minute Setup
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx.git
|
||||
cd MokoOnyx
|
||||
```
|
||||
|
||||
### 2. Install Development Dependencies
|
||||
|
||||
```bash
|
||||
# Using Make (recommended)
|
||||
make dev-setup
|
||||
|
||||
# Or manually
|
||||
composer global require "squizlabs/php_codesniffer:^3.0"
|
||||
composer global require phpstan/phpstan
|
||||
composer global require "phpcompatibility/php-compatibility:^9.0"
|
||||
composer global require codeception/codeception
|
||||
```
|
||||
|
||||
### 3. Validate Everything Works
|
||||
|
||||
```bash
|
||||
# Quick validation
|
||||
make validate-required
|
||||
|
||||
# Or comprehensive validation
|
||||
make validate
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Development Workflow
|
||||
|
||||
```bash
|
||||
# 1. Make your changes
|
||||
vim src/index.php
|
||||
|
||||
# 2. Validate locally
|
||||
make validate-required
|
||||
|
||||
# 3. Check code quality
|
||||
make quality
|
||||
|
||||
# 4. Commit
|
||||
git add -A
|
||||
git commit -m "feat: add new feature"
|
||||
# (pre-commit hook runs automatically)
|
||||
|
||||
# 5. Push
|
||||
git push origin your-branch
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
make test
|
||||
|
||||
# Run unit tests only
|
||||
make test-unit
|
||||
|
||||
# Run acceptance tests only
|
||||
make test-acceptance
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
|
||||
```bash
|
||||
# Check everything
|
||||
make quality
|
||||
|
||||
# PHP CodeSniffer only
|
||||
make phpcs
|
||||
|
||||
# Auto-fix PHPCS issues
|
||||
make phpcs-fix
|
||||
|
||||
# PHPStan only
|
||||
make phpstan
|
||||
|
||||
# PHP compatibility check
|
||||
make phpcompat
|
||||
```
|
||||
|
||||
### Creating a Release Package
|
||||
|
||||
```bash
|
||||
# Package with auto-detected version
|
||||
make package
|
||||
|
||||
# Check package contents
|
||||
ls -lh dist/
|
||||
unzip -l dist/mokoonyx-*.zip
|
||||
```
|
||||
|
||||
## VS Code Integration
|
||||
|
||||
If using VS Code, press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac) and type "Run Task" to see available tasks:
|
||||
|
||||
- **Validate All** - Run all validation scripts (default test task)
|
||||
- **Validate Required** - Run only required validations
|
||||
- **PHP CodeSniffer** - Check code style
|
||||
- **PHP CodeSniffer - Auto Fix** - Fix code style issues
|
||||
- **PHPStan** - Static analysis
|
||||
- **Run Tests** - Execute all tests
|
||||
- **Create Package** - Build distribution ZIP
|
||||
- **Install Git Hooks** - Set up pre-commit hooks
|
||||
|
||||
## Available Make Commands
|
||||
|
||||
Run `make help` to see all available commands:
|
||||
|
||||
```bash
|
||||
make help # Show all commands
|
||||
make dev-setup # Complete environment setup
|
||||
make validate # Run all validations
|
||||
make test # Run all tests
|
||||
make quality # Check code quality
|
||||
make package # Create distribution package
|
||||
make clean # Remove generated files
|
||||
make check # Quick check (validate + quality)
|
||||
make all # Complete build pipeline
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
moko-cassiopeia/
|
||||
├── src/ # Joomla template source (template root)
|
||||
│ ├── component.php # Component template file
|
||||
│ ├── index.php # Main template file
|
||||
│ ├── offline.php # Offline page template
|
||||
│ ├── error.php # Error page template
|
||||
│ ├── templateDetails.xml # Template manifest
|
||||
│ ├── html/ # Module & component overrides
|
||||
│ ├── media/ # Assets (CSS, JS, images, fonts)
|
||||
│ ├── language/ # Frontend language files (en-GB, en-US)
|
||||
│ └── administrator/ # Backend files
|
||||
│ └── language/ # Backend language files
|
||||
├── tests/ # Test suites
|
||||
├── docs/ # Documentation
|
||||
├── scripts/ # Build scripts
|
||||
├── .github/workflows/ # CI/CD workflows
|
||||
├── Makefile # Make commands
|
||||
└── README.md # Project overview
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Learning the Workflow
|
||||
|
||||
1. **Read the Workflow Guide**: [docs/WORKFLOW_GUIDE.md](./WORKFLOW_GUIDE.md)
|
||||
2. **Review Joomla Development**: [docs/JOOMLA_DEVELOPMENT.md](./JOOMLA_DEVELOPMENT.md)
|
||||
|
||||
### Creating Your First Feature
|
||||
|
||||
1. **Create a version branch** via GitHub Actions:
|
||||
- Go to Actions → Create version branch
|
||||
- Enter version (e.g., 03.06.00)
|
||||
- Select branch prefix: `dev/`
|
||||
- Run workflow
|
||||
|
||||
2. **Checkout the branch**:
|
||||
```bash
|
||||
git fetch origin
|
||||
git checkout dev/03.06.00
|
||||
```
|
||||
|
||||
3. **Make changes and test**:
|
||||
```bash
|
||||
# Edit files
|
||||
vim src/index.php
|
||||
|
||||
# Validate
|
||||
make validate-required
|
||||
|
||||
# Check quality
|
||||
make quality
|
||||
```
|
||||
|
||||
4. **Commit and push**:
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "feat: your feature description"
|
||||
git push origin dev/03.06.00
|
||||
```
|
||||
|
||||
5. **Watch CI**: Check GitHub Actions for automated testing
|
||||
|
||||
### Understanding the Release Process
|
||||
|
||||
```
|
||||
Development → RC → Stable → Production
|
||||
(dev/) (rc/) (version/) (main)
|
||||
```
|
||||
|
||||
1. **dev/X.Y.Z** - Active development
|
||||
2. **rc/X.Y.Z** - Release candidate testing
|
||||
3. **version/X.Y.Z** - Stable release
|
||||
4. **main** - Production (auto-merged from version/)
|
||||
|
||||
Use the Release Pipeline workflow to promote between stages.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Scripts Not Executable
|
||||
|
||||
```bash
|
||||
make fix-permissions
|
||||
### PHPStan/PHPCS Not Found
|
||||
|
||||
```bash
|
||||
make install
|
||||
# Or manually:
|
||||
composer global require "squizlabs/php_codesniffer:^3.0" phpstan/phpstan
|
||||
```
|
||||
|
||||
### CI Workflow Fails
|
||||
|
||||
1. Check the workflow logs in GitHub Actions
|
||||
2. Run validation locally:
|
||||
```bash
|
||||
make validate-required
|
||||
make quality
|
||||
```
|
||||
|
||||
### Need Help?
|
||||
|
||||
- **Documentation**: Check [docs/](../docs/) directory
|
||||
- **Issues**: Open an issue on GitHub
|
||||
- **Contributing**: See [CONTRIBUTING.md](../CONTRIBUTING.md)
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Before Committing
|
||||
|
||||
```bash
|
||||
# Always validate first
|
||||
make validate-required
|
||||
|
||||
# Check quality for PHP changes
|
||||
make quality
|
||||
|
||||
# Run tests if you changed functionality
|
||||
make test
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
- Follow PSR-12 standards
|
||||
- Use `make phpcs-fix` to auto-fix issues
|
||||
- Add SPDX license headers to new files
|
||||
- Keep functions small and focused
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update docs when changing workflows
|
||||
- Add comments for complex logic
|
||||
- Update CHANGELOG.md with changes
|
||||
- Keep README.md current
|
||||
|
||||
### Version Management
|
||||
|
||||
- Use semantic versioning: Major.Minor.Patch (03.06.00)
|
||||
- Update CHANGELOG.md with all changes
|
||||
- Follow the version hierarchy: dev → rc → version → main
|
||||
- Never skip stages in the release process
|
||||
|
||||
## Useful Resources
|
||||
|
||||
- [Joomla Documentation](https://docs.joomla.org/)
|
||||
- [PSR-12 Coding Standard](https://www.php-fig.org/psr/psr-12/)
|
||||
- [Semantic Versioning](https://semver.org/)
|
||||
- [Conventional Commits](https://www.conventionalcommits.org/)
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
```bash
|
||||
# Setup
|
||||
make dev-setup # Initial setup
|
||||
|
||||
# Development
|
||||
make validate-required # Quick validation
|
||||
make quality # Code quality
|
||||
make test # Run tests
|
||||
|
||||
# Building
|
||||
make package # Create ZIP
|
||||
|
||||
# Maintenance
|
||||
make clean # Clean generated files
|
||||
make fix-permissions # Fix script permissions
|
||||
|
||||
# Help
|
||||
make help # Show all commands
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* Document: docs/QUICK_START.md
|
||||
* Repository: [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* Path: /docs/QUICK_START.md
|
||||
* Owner: Moko Consulting
|
||||
* Version: 03.06.03
|
||||
* Status: Active
|
||||
* Effective Date: 2026-01-30
|
||||
* Classification: Public Open Source Documentation
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ----------------------------------------------------- | --------------- |
|
||||
| 2026-01-30 | Updated metadata to MokoStandards format | GitHub Copilot |
|
||||
| 2025-01-04 | Initial quick start guide created | GitHub Copilot |
|
||||
188
docs/README.md
Normal file
188
docs/README.md
Normal file
@@ -0,0 +1,188 @@
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoOnyx.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: docs/README.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Documentation index for MokoOnyx template
|
||||
PATH: /docs/README.md
|
||||
-->
|
||||
|
||||
# MokoOnyx Documentation
|
||||
|
||||
This directory contains comprehensive documentation for the MokoOnyx Joomla template.
|
||||
|
||||
## Documentation Overview
|
||||
|
||||
### Developer Documentation
|
||||
|
||||
* **[Quick Start Guide](QUICK_START.md)** - Get up and running in 5 minutes
|
||||
* Development environment setup
|
||||
* Essential commands and workflows
|
||||
* First-time contributor guide
|
||||
|
||||
* **[Workflow Guide](WORKFLOW_GUIDE.md)** - Complete workflow reference
|
||||
* Git branching strategy
|
||||
* Development workflow
|
||||
* Pull request guidelines
|
||||
|
||||
* **[Release Process](RELEASE_PROCESS.md)** ⭐ - Complete release documentation
|
||||
* Automated release workflow with GitHub Actions
|
||||
* Manual release procedures
|
||||
* Update server configuration
|
||||
* Testing and rollback procedures
|
||||
* Build scripts and tools
|
||||
|
||||
* **[Joomla Development Guide](JOOMLA_DEVELOPMENT.md)** - Joomla-specific development
|
||||
* Testing with Codeception
|
||||
* PHP quality checks (PHPStan, PHPCS)
|
||||
* Joomla extension packaging
|
||||
* Multi-version testing
|
||||
|
||||
* **[Manual Deployment Guide](MANUAL_DEPLOYMENT.md)** - Deploy src directory without building
|
||||
* Understanding src vs. installed structure
|
||||
* Manual deployment methods (copy, symlink)
|
||||
* Troubleshooting language files and media
|
||||
* Best practices for development deployments
|
||||
|
||||
* **[CSS Variables Reference](CSS_VARIABLES.md)** - Complete CSS customization guide
|
||||
* All available CSS variables
|
||||
* Custom color palette creation
|
||||
* Usage examples and tips
|
||||
* Light and dark mode theming
|
||||
|
||||
* **[Module & Component Overrides](MODULE_OVERRIDES.md)** - Mobile-responsive overrides guide
|
||||
* 16 module overrides + 12 component overrides
|
||||
* VirtueMart, Community Builder, JEM, Kunena, industry extensions
|
||||
* Mobile-first responsive design patterns
|
||||
* Accessibility features and customization
|
||||
|
||||
* **[Override Philosophy](OVERRIDE_PHILOSOPHY.md)** ⭐ - Alternative layouts, not replacements
|
||||
* Why overrides use `mobile.php` naming instead of `default.php`
|
||||
* How to activate alternative layouts in Joomla
|
||||
* Benefits of non-replacing overrides
|
||||
* Developer guidelines and best practices
|
||||
|
||||
* **[Roadmap](ROADMAP.md)** - Version-specific roadmap
|
||||
* Current features (v03.07.00)
|
||||
* Feature evolution timeline
|
||||
* Planned enhancements
|
||||
* Development priorities
|
||||
|
||||
### User Documentation
|
||||
|
||||
For end-user documentation, installation instructions, and feature guides, see the main [README.md](../README.md) in the repository root.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
moko-cassiopeia/
|
||||
├── docs/ # Documentation (you are here)
|
||||
│ ├── README.md # This file - documentation index
|
||||
│ ├── QUICK_START.md # Quick start guide for developers
|
||||
│ ├── WORKFLOW_GUIDE.md # Development workflow guide
|
||||
│ ├── JOOMLA_DEVELOPMENT.md # Joomla-specific development guide
|
||||
│ ├── CSS_VARIABLES.md # CSS variables reference
|
||||
│ ├── MODULE_OVERRIDES.md # Module & component overrides guide
|
||||
│ └── ROADMAP.md # Version-specific roadmap
|
||||
├── src/ # Template source code (Joomla template root)
|
||||
│ ├── component.php # Component template
|
||||
│ ├── index.php # Main template file
|
||||
│ ├── offline.php # Offline template
|
||||
│ ├── error.php # Error page template
|
||||
│ ├── templateDetails.xml # Template manifest
|
||||
│ ├── html/ # Module & component overrides (16 modules, 12 components)
|
||||
│ ├── media/ # Assets (CSS, JS, images, fonts)
|
||||
│ │ ├── css/ # Stylesheets
|
||||
│ │ │ └── colors/ # Color schemes
|
||||
│ │ │ ├── light/ # Light mode color files (colors_standard.css)
|
||||
│ │ │ └── dark/ # Dark mode color files (colors_standard.css)
|
||||
│ │ ├── js/ # JavaScript files
|
||||
│ │ ├── images/ # Image assets
|
||||
│ │ └── fonts/ # Font files
|
||||
│ ├── language/ # Frontend language files
|
||||
│ │ ├── en-GB/ # English (UK) translations
|
||||
│ │ └── en-US/ # English (US) translations
|
||||
│ └── administrator/ # Backend files
|
||||
│ └── language/ # Backend language files
|
||||
│ ├── en-GB/ # English (UK) system translations
|
||||
│ └── en-US/ # English (US) system translations
|
||||
├── templates/ # Reserved for future template files
|
||||
│ └── README.md # Templates directory guide
|
||||
├── scripts/ # Build and utility scripts
|
||||
├── tests/ # Automated tests
|
||||
└── .github/ # GitHub configuration and workflows
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Before contributing, please read:
|
||||
|
||||
1. **[CONTRIBUTING.md](../CONTRIBUTING.md)** - Contribution guidelines and standards
|
||||
2. **[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)** - Community standards and expectations
|
||||
3. **[SECURITY.md](../SECURITY.md)** - Security policy and reporting procedures
|
||||
|
||||
## Standards Compliance
|
||||
|
||||
This project adheres to [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards) for:
|
||||
|
||||
* Coding standards and formatting
|
||||
* Documentation requirements
|
||||
* Git workflow and branching
|
||||
* CI/CD pipeline configuration
|
||||
* Security scanning and dependency management
|
||||
|
||||
## Additional Resources
|
||||
|
||||
* **Repository**: [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* **Issue Tracker**: [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||
* **Changelog**: [CHANGELOG.md](../CHANGELOG.md)
|
||||
* **License**: [GPL-3.0-or-later](../LICENSE)
|
||||
|
||||
## Support
|
||||
|
||||
* **Email**: hello@mokoconsulting.tech
|
||||
* **Website**: https://mokoconsulting.tech/support/joomla-cms/moko-cassiopeia-roadmap
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* Document: docs/README.md
|
||||
* Repository: [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* Path: /docs/README.md
|
||||
* Owner: Moko Consulting
|
||||
* Version: 03.07.00
|
||||
* Status: Active
|
||||
* Effective Date: 2026-01-30
|
||||
* Classification: Public Open Source Documentation
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ----------------------------------------------------- | --------------- |
|
||||
| 2026-02-22 | Added MODULE_OVERRIDES.md reference, updated version to 03.07.00 | GitHub Copilot |
|
||||
| 2026-01-30 | Added CSS Variables reference, updated version to 03.06.03 | GitHub Copilot |
|
||||
| 2026-01-09 | Initial documentation index created for MokoStandards compliance. | GitHub Copilot |
|
||||
| 2026-01-27 | Updated with roadmap link and version to 03.05.01. | GitHub Copilot |
|
||||
638
docs/RELEASE_PROCESS.md
Normal file
638
docs/RELEASE_PROCESS.md
Normal file
@@ -0,0 +1,638 @@
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoOnyx.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: docs/RELEASE_PROCESS.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Complete release process documentation for MokoOnyx
|
||||
PATH: /docs/RELEASE_PROCESS.md
|
||||
-->
|
||||
|
||||
# Release Process — MokoOnyx
|
||||
|
||||
This document describes the complete release process for MokoOnyx Joomla template, including automated workflows and manual procedures.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Release Types](#release-types)
|
||||
3. [Automated Release Process](#automated-release-process)
|
||||
4. [Manual Release Process](#manual-release-process)
|
||||
5. [Update Server Configuration](#update-server-configuration)
|
||||
6. [Testing Releases](#testing-releases)
|
||||
7. [Rollback Procedures](#rollback-procedures)
|
||||
8. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
MokoOnyx uses an automated release system powered by GitHub Actions. The system:
|
||||
|
||||
- **Builds** installation packages automatically
|
||||
- **Generates** checksums for security verification
|
||||
- **Creates** GitHub Releases with downloadable artifacts
|
||||
- **Updates** the Joomla update server (`updates.xml`) automatically
|
||||
- **Validates** package integrity with SHA-256 hashes
|
||||
|
||||
### Key Components
|
||||
|
||||
1. **Release Workflow** (`.github/workflows/release.yml`): Builds and publishes releases
|
||||
2. **Auto-Update SHA** (`.github/workflows/auto-update-sha.yml`): Updates `updates.xml` after release
|
||||
3. **Build Script** (`scripts/build-release.sh`): Local development builds
|
||||
4. **Update Server** (`updates.xml`): Joomla update server manifest
|
||||
|
||||
---
|
||||
|
||||
## Release Types
|
||||
|
||||
### Patch Release (Third Digit)
|
||||
|
||||
**Format**: `XX.XX.XX` → `XX.XX.XX+1` (e.g., `03.08.03` → `03.08.04`)
|
||||
|
||||
**When to use**:
|
||||
- Bug fixes
|
||||
- Security patches
|
||||
- Documentation updates
|
||||
- Minor CSS/styling tweaks
|
||||
- No breaking changes
|
||||
|
||||
**Example**: `03.08.03` → `03.08.04`
|
||||
|
||||
### Minor Release (Second Digit)
|
||||
|
||||
**Format**: `XX.XX.00` → `XX.XX+1.00` (e.g., `03.08.03` → `03.09.00`)
|
||||
|
||||
**When to use**:
|
||||
- New features
|
||||
- New module/component overrides
|
||||
- Significant styling changes
|
||||
- Backward-compatible changes
|
||||
|
||||
**Example**: `03.08.03` → `03.09.00`
|
||||
|
||||
### Major Release (First Digit)
|
||||
|
||||
**Format**: `XX.00.00` → `XX+1.00.00` (e.g., `03.08.03` → `04.00.00`)
|
||||
|
||||
**When to use**:
|
||||
- Breaking changes
|
||||
- Major architecture changes
|
||||
- Joomla version upgrades
|
||||
- Complete redesigns
|
||||
|
||||
**Example**: `03.08.03` → `04.00.00`
|
||||
|
||||
---
|
||||
|
||||
## Automated Release Process
|
||||
|
||||
**Recommended for most releases**
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [ ] All changes merged to `main` branch
|
||||
- [ ] Tests passing
|
||||
- [ ] Documentation updated
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] Local testing completed
|
||||
|
||||
### Step 1: Prepare Release Branch
|
||||
|
||||
```bash
|
||||
# Create release branch
|
||||
git checkout main
|
||||
git pull
|
||||
git checkout -b release/03.08.04
|
||||
|
||||
# Update version in templateDetails.xml
|
||||
# Edit: src/templateDetails.xml
|
||||
# Change: <version>03.08.03</version>
|
||||
# To: <version>03.08.04</version>
|
||||
|
||||
# Update CHANGELOG.md
|
||||
# Add new section:
|
||||
## [03.08.04] - 2026-02-27
|
||||
|
||||
### Added
|
||||
- Feature descriptions
|
||||
|
||||
### Fixed
|
||||
- Bug fix descriptions
|
||||
|
||||
### Changed
|
||||
- Change descriptions
|
||||
|
||||
# Commit changes
|
||||
git add src/templateDetails.xml CHANGELOG.md
|
||||
git commit -m "chore: Prepare release 03.08.04"
|
||||
git push origin release/03.08.04
|
||||
```
|
||||
|
||||
### Step 2: Create Pull Request
|
||||
|
||||
1. Go to GitHub repository
|
||||
2. Click "Pull requests" → "New pull request"
|
||||
3. Base: `main`, Compare: `release/03.08.04`
|
||||
4. Title: `Release 03.08.04`
|
||||
5. Description: Copy relevant CHANGELOG entries
|
||||
6. Create pull request
|
||||
7. Review and merge
|
||||
|
||||
### Step 3: Create and Push Tag
|
||||
|
||||
```bash
|
||||
# Switch to main and pull changes
|
||||
git checkout main
|
||||
git pull
|
||||
|
||||
# Create tag
|
||||
git tag 03.08.04
|
||||
|
||||
# Push tag (triggers release workflow)
|
||||
git push origin 03.08.04
|
||||
```
|
||||
|
||||
### Step 4: Monitor Automated Process
|
||||
|
||||
1. **Go to GitHub Actions tab**
|
||||
2. **Watch "Create Release" workflow**:
|
||||
- Builds package
|
||||
- Generates checksums
|
||||
- Creates GitHub Release
|
||||
- Uploads artifacts
|
||||
|
||||
3. **Watch "Auto-Update SHA Hash" workflow**:
|
||||
- Downloads release package
|
||||
- Calculates SHA-256 hash
|
||||
- Updates `updates.xml`
|
||||
- Commits to main branch
|
||||
|
||||
### Step 5: Verify Release
|
||||
|
||||
1. **Check GitHub Release**:
|
||||
- Go to Releases tab
|
||||
- Verify release `03.08.04` exists
|
||||
- Download ZIP package
|
||||
- Verify checksums match
|
||||
|
||||
2. **Check updates.xml**:
|
||||
```bash
|
||||
git pull
|
||||
cat updates.xml
|
||||
```
|
||||
- Verify version is `03.08.04`
|
||||
- Verify download URL is correct
|
||||
- Verify SHA-256 hash is present
|
||||
|
||||
3. **Test Joomla Update**:
|
||||
- Install previous version in Joomla
|
||||
- Go to Extensions → Update
|
||||
- Verify update is detected
|
||||
- Perform update
|
||||
- Verify template works correctly
|
||||
|
||||
---
|
||||
|
||||
## Manual Release Process
|
||||
|
||||
**Use when automation fails or for local testing**
|
||||
|
||||
### Step 1: Prepare Repository
|
||||
|
||||
```bash
|
||||
# Update version numbers
|
||||
# Edit: src/templateDetails.xml
|
||||
# Edit: CHANGELOG.md
|
||||
|
||||
# Commit changes
|
||||
git add src/templateDetails.xml CHANGELOG.md
|
||||
git commit -m "chore: Prepare release 03.08.04"
|
||||
git push
|
||||
```
|
||||
|
||||
### Step 2: Build Package Locally
|
||||
|
||||
```bash
|
||||
# Run build script
|
||||
./scripts/build-release.sh 03.08.04
|
||||
|
||||
# Output will be in build/ directory:
|
||||
# - mokoonyx-src-03.08.04.zip
|
||||
# - mokoonyx-src-03.08.04.zip.sha256
|
||||
# - mokoonyx-src-03.08.04.zip.md5
|
||||
```
|
||||
|
||||
### Step 3: Test Package
|
||||
|
||||
```bash
|
||||
# Install in Joomla test environment
|
||||
# Extensions → Manage → Install → Upload Package File
|
||||
# Select: build/mokoonyx-src-03.08.04.zip
|
||||
|
||||
# Test all features:
|
||||
# - Template displays correctly
|
||||
# - Module overrides work
|
||||
# - Alternative layouts selectable
|
||||
# - Dark mode works
|
||||
# - No JavaScript errors
|
||||
```
|
||||
|
||||
### Step 4: Create GitHub Release
|
||||
|
||||
1. **Go to GitHub Releases**
|
||||
2. **Click "Create a new release"**
|
||||
3. **Tag**: `03.08.04` (create new tag)
|
||||
4. **Release title**: `Release 03.08.04`
|
||||
5. **Description**: Copy from CHANGELOG.md
|
||||
6. **Upload files**:
|
||||
- `mokoonyx-src-03.08.04.zip`
|
||||
- `mokoonyx-src-03.08.04.zip.sha256`
|
||||
- `mokoonyx-src-03.08.04.zip.md5`
|
||||
7. **Publish release**
|
||||
|
||||
### Step 5: Update updates.xml Manually
|
||||
|
||||
```bash
|
||||
# Extract SHA-256 hash
|
||||
cat build/mokoonyx-src-03.08.04.zip.sha256
|
||||
# Example output: a1b2c3d4e5f6...
|
||||
|
||||
# Edit updates.xml
|
||||
# Update <version>03.08.04</version>
|
||||
# Update <creationDate>2026-02-27</creationDate>
|
||||
# Update <downloadurl>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/03.08.04/mokoonyx-src-03.08.04.zip</downloadurl>
|
||||
# Update <sha256>sha256:a1b2c3d4e5f6...</sha256>
|
||||
|
||||
# Commit and push
|
||||
git add updates.xml
|
||||
git commit -m "chore: Update updates.xml for release 03.08.04"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update Server Configuration
|
||||
|
||||
### updates.xml Structure
|
||||
|
||||
```xml
|
||||
<updates>
|
||||
<update>
|
||||
<name>MokoOnyx</name>
|
||||
<description>Moko Consulting's site template based on Cassiopeia.</description>
|
||||
<element>mokoonyx</element>
|
||||
<type>template</type>
|
||||
<client>site</client>
|
||||
|
||||
<version>03.08.04</version>
|
||||
<creationDate>2026-02-27</creationDate>
|
||||
<author>Jonathan Miller || Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<copyright>(C)GNU General Public License Version 3 - 2026 Moko Consulting</copyright>
|
||||
|
||||
<infourl title='MokoOnyx'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx</infourl>
|
||||
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/03.08.04/mokoonyx-src-03.08.04.zip</downloadurl>
|
||||
<sha256>sha256:a1b2c3d4e5f6...</sha256>
|
||||
</downloads>
|
||||
|
||||
<tags>
|
||||
<tag>stable</tag>
|
||||
</tags>
|
||||
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://www.mokoconsulting.tech</maintainerurl>
|
||||
|
||||
<targetplatform name='joomla' version='5.*'/>
|
||||
</update>
|
||||
</updates>
|
||||
```
|
||||
|
||||
### Hosting Update Server
|
||||
|
||||
The `updates.xml` file is hosted directly on GitHub:
|
||||
|
||||
**URL**: `https://raw.githubusercontent.com/mokoconsulting-tech/MokoOnyx/main/updates.xml`
|
||||
|
||||
This URL is configured in `src/templateDetails.xml`:
|
||||
|
||||
```xml
|
||||
<updateservers>
|
||||
<server type="extension" name="MokoOnyx Updates" priority="1">
|
||||
https://raw.githubusercontent.com/mokoconsulting-tech/MokoOnyx/main/updates.xml
|
||||
</server>
|
||||
</updateservers>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Releases
|
||||
|
||||
### Pre-Release Testing
|
||||
|
||||
```bash
|
||||
# 1. Build package locally
|
||||
./scripts/build-release.sh
|
||||
|
||||
# 2. Set up Joomla test environment
|
||||
# - Clean Joomla 5.x installation
|
||||
# - Previous MokoOnyx version installed
|
||||
|
||||
# 3. Test current version features
|
||||
# - All module overrides
|
||||
# - Alternative layouts
|
||||
# - Dark mode toggle
|
||||
# - Responsive behavior
|
||||
|
||||
# 4. Install new package
|
||||
# Extensions → Manage → Install → Upload Package
|
||||
|
||||
# 5. Verify upgrade process
|
||||
# - No errors during installation
|
||||
# - Settings preserved
|
||||
# - Custom modifications retained
|
||||
|
||||
# 6. Test new features
|
||||
# - New functionality works
|
||||
# - Bug fixes applied
|
||||
# - No regressions
|
||||
```
|
||||
|
||||
### Update Server Testing
|
||||
|
||||
```bash
|
||||
# 1. Install previous version in Joomla
|
||||
# 2. Go to: Extensions → Update
|
||||
# 3. Click "Find Updates"
|
||||
# 4. Verify update shows: "MokoOnyx 03.08.04"
|
||||
# 5. Click "Update"
|
||||
# 6. Verify successful update
|
||||
# 7. Test template functionality
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Package installs without errors
|
||||
- [ ] Template activates correctly
|
||||
- [ ] All module overrides work
|
||||
- [ ] Alternative layouts selectable
|
||||
- [ ] Dark mode functions
|
||||
- [ ] Responsive on mobile/tablet/desktop
|
||||
- [ ] No JavaScript console errors
|
||||
- [ ] No PHP errors in Joomla logs
|
||||
- [ ] Update server detects new version
|
||||
- [ ] Update process completes successfully
|
||||
|
||||
---
|
||||
|
||||
## Rollback Procedures
|
||||
|
||||
### Rollback Release
|
||||
|
||||
If a release has critical issues:
|
||||
|
||||
1. **Delete GitHub Release**:
|
||||
- Go to Releases
|
||||
- Click release to delete
|
||||
- Click "Delete"
|
||||
- Confirm deletion
|
||||
|
||||
2. **Delete Git Tag**:
|
||||
```bash
|
||||
# Delete local tag
|
||||
git tag -d 03.08.04
|
||||
|
||||
# Delete remote tag
|
||||
git push --delete origin 03.08.04
|
||||
```
|
||||
|
||||
3. **Revert updates.xml**:
|
||||
```bash
|
||||
# Revert to previous version
|
||||
git revert <commit-hash-of-auto-update>
|
||||
git push
|
||||
```
|
||||
|
||||
4. **Notify Users**:
|
||||
- Create GitHub issue explaining the problem
|
||||
- Pin the issue
|
||||
- Provide rollback instructions for users
|
||||
|
||||
### User Rollback Instructions
|
||||
|
||||
For users who installed the problematic version:
|
||||
|
||||
1. **Download previous version** from GitHub Releases
|
||||
2. **Uninstall current version**:
|
||||
- Extensions → Manage → Manage
|
||||
- Find MokoOnyx
|
||||
- Click "Uninstall"
|
||||
3. **Install previous version**:
|
||||
- Extensions → Manage → Install
|
||||
- Upload previous version ZIP
|
||||
4. **Verify functionality**
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Release Workflow Fails
|
||||
|
||||
**Problem**: Build fails with "rsync: command not found"
|
||||
|
||||
**Solution**: The GitHub Actions runner always has rsync installed. If this occurs, check the workflow file syntax.
|
||||
|
||||
**Problem**: ZIP creation fails
|
||||
|
||||
**Solution**: Check that `src/` and `src/media/` directories exist and contain files.
|
||||
|
||||
**Problem**: Version update fails
|
||||
|
||||
**Solution**: Verify `sed` commands in workflow match actual XML structure.
|
||||
|
||||
### Auto-Update SHA Fails
|
||||
|
||||
**Problem**: Cannot download release package
|
||||
|
||||
**Solution**:
|
||||
- Verify release was published (not draft)
|
||||
- Check package naming: `mokoonyx-src-{version}.zip`
|
||||
- Verify release tag format
|
||||
|
||||
**Problem**: SHA-256 hash mismatch
|
||||
|
||||
**Solution**:
|
||||
- Package may have been modified after calculation
|
||||
- Re-run the workflow manually
|
||||
- Verify package integrity
|
||||
|
||||
**Problem**: Commit fails
|
||||
|
||||
**Solution**:
|
||||
- Check workflow has write permissions
|
||||
- Verify no branch protection rules blocking bot commits
|
||||
|
||||
### Manual Build Issues
|
||||
|
||||
**Problem**: `./scripts/build-release.sh: Permission denied`
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
chmod +x scripts/build-release.sh
|
||||
./scripts/build-release.sh
|
||||
```
|
||||
|
||||
**Problem**: Build directory exists
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
rm -rf build/
|
||||
./scripts/build-release.sh
|
||||
```
|
||||
|
||||
### Update Server Issues
|
||||
|
||||
**Problem**: Joomla doesn't detect update
|
||||
|
||||
**Solution**:
|
||||
1. Check `updates.xml` is accessible:
|
||||
```bash
|
||||
curl https://raw.githubusercontent.com/mokoconsulting-tech/MokoOnyx/main/updates.xml
|
||||
```
|
||||
2. Verify version number is higher than installed version
|
||||
3. Clear Joomla cache: System → Clear Cache
|
||||
4. Check update URL in templateDetails.xml
|
||||
|
||||
**Problem**: Update fails with "Invalid package"
|
||||
|
||||
**Solution**:
|
||||
- Verify SHA-256 hash matches
|
||||
- Re-download package and check integrity
|
||||
- Verify package structure is correct
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Version Numbering
|
||||
|
||||
- **Always increment** version numbers sequentially
|
||||
- **Never reuse** version numbers
|
||||
- **Use consistent** format: `XX.XX.XX`
|
||||
|
||||
### Changelog
|
||||
|
||||
- **Update before** release
|
||||
- **Include all changes** since last version
|
||||
- **Categorize** changes: Added, Changed, Fixed, Removed
|
||||
- **Write clear descriptions** for users
|
||||
|
||||
### Testing
|
||||
|
||||
- **Test locally** before pushing tag
|
||||
- **Test update process** from previous version
|
||||
- **Test on clean** Joomla installation
|
||||
- **Test different** configurations
|
||||
|
||||
### Communication
|
||||
|
||||
- **Announce releases** on GitHub Discussions
|
||||
- **Document breaking changes** clearly
|
||||
- **Provide migration guides** for major changes
|
||||
- **Respond promptly** to issue reports
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Automated Release Commands
|
||||
|
||||
```bash
|
||||
# 1. Create release branch
|
||||
git checkout -b release/03.08.04
|
||||
|
||||
# 2. Update version and CHANGELOG
|
||||
# (edit files)
|
||||
|
||||
# 3. Commit and push
|
||||
git add .
|
||||
git commit -m "chore: Prepare release 03.08.04"
|
||||
git push origin release/03.08.04
|
||||
|
||||
# 4. Create and merge PR (via GitHub UI)
|
||||
|
||||
# 5. Create and push tag
|
||||
git checkout main
|
||||
git pull
|
||||
git tag 03.08.04
|
||||
git push origin 03.08.04
|
||||
|
||||
# 6. Wait for automation to complete
|
||||
```
|
||||
|
||||
### Manual Release Commands
|
||||
|
||||
```bash
|
||||
# Build locally
|
||||
./scripts/build-release.sh 03.08.04
|
||||
|
||||
# Test installation
|
||||
# (manual Joomla testing)
|
||||
|
||||
# Create release on GitHub
|
||||
# (via GitHub UI)
|
||||
|
||||
# Update updates.xml
|
||||
# (edit file with SHA-256)
|
||||
git add updates.xml
|
||||
git commit -m "chore: Update updates.xml for 03.08.04"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **Build Scripts**: [scripts/README.md](../scripts/README.md)
|
||||
- **Workflow Guide**: [WORKFLOW_GUIDE.md](WORKFLOW_GUIDE.md)
|
||||
- **Contributing**: [CONTRIBUTING.md](../CONTRIBUTING.md)
|
||||
- **Changelog**: [CHANGELOG.md](../CHANGELOG.md)
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
- **Issues**: [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/issues)
|
||||
- **Discussions**: [GitHub Discussions](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/discussions)
|
||||
- **Email**: hello@mokoconsulting.tech
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2026 Moko Consulting
|
||||
|
||||
This documentation is licensed under GPL-3.0-or-later.
|
||||
917
docs/ROADMAP.md
Normal file
917
docs/ROADMAP.md
Normal file
@@ -0,0 +1,917 @@
|
||||
<!--
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoOnyx.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia
|
||||
FILE: docs/ROADMAP.md
|
||||
VERSION: 03.09.03
|
||||
BRIEF: Version-specific roadmap for MokoOnyx template
|
||||
PATH: /docs/ROADMAP.md
|
||||
-->
|
||||
|
||||
# MokoOnyx Roadmap (VERSION: 03.09.03)
|
||||
|
||||
This document provides a comprehensive, version-specific roadmap for the MokoOnyx Joomla template, tracking feature evolution, current capabilities, and planned enhancements.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Version Timeline](#version-timeline)
|
||||
- [Past Releases](#past-releases)
|
||||
- [Future Roadmap (5-Year Plan)](#future-roadmap-5-year-plan)
|
||||
- [Current Release (v03.06.03)](#current-release-v030603)
|
||||
- [Implemented Features](#implemented-features)
|
||||
- [Planned Features](#planned-features)
|
||||
- [Development Priorities](#development-priorities)
|
||||
- [Long-term Vision](#long-term-vision)
|
||||
- [External Resources](#external-resources)
|
||||
|
||||
---
|
||||
|
||||
## Version Timeline
|
||||
|
||||
### Past Releases
|
||||
|
||||
### v03.05.01 (2026-01-09) - Standards & Security
|
||||
**Status**: Released (CHANGELOG entry exists, code files pending version update)
|
||||
|
||||
**Added**:
|
||||
- Dependency review workflow for vulnerability scanning
|
||||
- Standards compliance workflow for MokoStandards validation
|
||||
- Dependabot configuration for automated security updates
|
||||
- Documentation index (`docs/README.md`)
|
||||
|
||||
**Changed**:
|
||||
- Removed custom CodeQL workflow (using GitHub's default setup)
|
||||
- Enforced repository compliance with MokoStandards
|
||||
- Improved security posture with automated scanning
|
||||
|
||||
### v03.06.00 (2026-01-28) - Version Update
|
||||
**Status**: Current Release (in code)
|
||||
|
||||
**Changed**:
|
||||
- Updated version to 03.06.00 across all files
|
||||
|
||||
### v03.05.00 (2026-01-04) - Workflow & Governance
|
||||
**Status**: Mentioned in CHANGELOG (v03.05.00)
|
||||
|
||||
**Added**:
|
||||
- `.github/workflows` directory structure
|
||||
- CODE_OF_CONDUCT.md from MokoStandards
|
||||
- CONTRIBUTING.md from MokoStandards
|
||||
|
||||
**Changed**:
|
||||
- TODO items to be split to separate file (tracked)
|
||||
|
||||
### v03.01.00 (2025-12-16) - CI/CD Foundation
|
||||
**Added**:
|
||||
- Initial GitHub Actions workflows
|
||||
|
||||
### v03.00.00 (2025-12-09) - Font Awesome 7 Upgrade
|
||||
**Updated**:
|
||||
- Copyright headers to MokoCodingDefaults standards
|
||||
- Fixed color style injection in `index.php`
|
||||
- Upgraded Font Awesome 6 to Font Awesome 7 Free
|
||||
- Added Font Awesome 7 Free style fallback
|
||||
|
||||
**Removed**:
|
||||
- Deprecated CODE_OF_CONDUCT.md
|
||||
- Deprecated CONTRIBUTING.md
|
||||
|
||||
### v02.01.05 (2025-09-04) - CSS Refinement
|
||||
**Fixed**:
|
||||
- Removed vmbasic.css
|
||||
- Repaired template.css and colors_standard.css
|
||||
|
||||
### v02.00.00 (2025-08-30) - Dark Mode & TOC
|
||||
**Major Features**:
|
||||
- **Dark Mode Toggle System**
|
||||
- Frontend toggle switch with localStorage persistence
|
||||
- Admin-configurable default mode
|
||||
- CSS rules for light/dark themes
|
||||
- JavaScript-powered mode switching
|
||||
|
||||
- **Enhanced Template Parameters**
|
||||
- Logo parameter support
|
||||
- GTM container ID configuration
|
||||
- Dark mode defaults in settings
|
||||
- Updated metadata and copyright headers
|
||||
|
||||
- **Expanded Table of Contents**
|
||||
- Automatic TOC injection
|
||||
- User-selectable placement (`toc-left` or `toc-right`)
|
||||
- Article options integration
|
||||
|
||||
**Improvements**:
|
||||
- Cleaned up `index.php` (removed duplicate skip-to-content calls)
|
||||
- Consolidated JavaScript asset loading
|
||||
- Streamlined CSS for toggle switch
|
||||
- Accessibility refinements (typography, color contrast)
|
||||
- Fixed missing logo parameter in header
|
||||
- Corrected stylesheet inconsistencies
|
||||
- Patched redundant script includes
|
||||
|
||||
### v01.00.00 - Initial Public Release
|
||||
**Core Features**:
|
||||
- Font Awesome 6 integration
|
||||
- Bootstrap 5 helpers and utilities
|
||||
- Automatic Table of Contents (TOC) utility
|
||||
- Moko Expansions: Google Tag Manager / GA4 hooks
|
||||
- Built on Joomla's Cassiopeia template
|
||||
|
||||
---
|
||||
|
||||
### Future Roadmap (5-Year Plan)
|
||||
|
||||
The following versions represent our planned annual major releases, each building upon the previous version's foundation.
|
||||
|
||||
#### v04.00.00 (Q4 2027) - Enhanced Accessibility & Performance
|
||||
**Status**: Planned
|
||||
**Target Release**: December 2027
|
||||
|
||||
**Major Template Features**:
|
||||
- **WCAG 2.1 AA Compliance**
|
||||
- Full accessibility audit and remediation
|
||||
- High-contrast theme options
|
||||
- Screen reader optimizations
|
||||
- Keyboard navigation enhancements
|
||||
- ARIA landmark improvements
|
||||
- Skip navigation enhancements
|
||||
|
||||
- **Template Performance Optimizations**
|
||||
- Critical CSS inlining for faster first paint
|
||||
- Lazy loading for images and below-fold content
|
||||
- WebP image support with automatic fallbacks
|
||||
- Advanced asset bundling and minification
|
||||
- Template asset caching (CSS/JS bundles)
|
||||
|
||||
- **Enhanced Layout System**
|
||||
- Additional responsive grid layouts
|
||||
- Flexible module position system
|
||||
- Column layout presets (2-col, 3-col, 4-col variations)
|
||||
- Grid/masonry article layouts
|
||||
- Sticky sidebar options
|
||||
|
||||
- **Typography Enhancements**
|
||||
- Advanced typography controls in template settings
|
||||
- Additional font pairing presets
|
||||
- Custom font upload support
|
||||
- Line height and letter spacing controls
|
||||
- Responsive typography scaling
|
||||
|
||||
- **Developer Experience**
|
||||
- Development mode enablement (unminified assets, debug output)
|
||||
- Live reload during development
|
||||
- Enhanced error logging and diagnostics
|
||||
- Template debugging tools
|
||||
- Style guide generator
|
||||
|
||||
- **Content Display Features**
|
||||
- Soft offline mode (category-based access during maintenance)
|
||||
- Enhanced article layouts (grid, masonry, timeline)
|
||||
- Image caption styling options
|
||||
- Quote block styling variations
|
||||
- Enhanced breadcrumb customization
|
||||
|
||||
**Template Infrastructure**:
|
||||
- Expanded template parameter validation
|
||||
- Enhanced template override detection
|
||||
- Automated template compatibility testing
|
||||
- Template performance profiling tools
|
||||
|
||||
---
|
||||
|
||||
#### v05.00.00 (Q4 2028) - Advanced Layouts & Template Customization
|
||||
**Status**: Planned
|
||||
**Target Release**: December 2028
|
||||
|
||||
**Major Template Features**:
|
||||
- **Enhanced Layout Builder**
|
||||
- Template-based page layout variations
|
||||
- Configurable layout options via template parameters
|
||||
- Layout presets library (blog, portfolio, business, magazine)
|
||||
- Module position layout manager
|
||||
- Visual layout preview in admin
|
||||
|
||||
- **Advanced Styling System**
|
||||
- Extended color palette management (unlimited custom palettes)
|
||||
- CSS variable editor in template settings
|
||||
- Style presets for different site types
|
||||
- Border radius and spacing controls
|
||||
- Box shadow and effect controls
|
||||
|
||||
- **Template Component Enhancements**
|
||||
- Enhanced menu styling options (mega menu support)
|
||||
- Advanced header variations (transparent, sticky, minimal)
|
||||
- Footer layout options (column variations, widgets)
|
||||
- Sidebar styling and behavior options
|
||||
- Hero section templates and variations
|
||||
|
||||
- **Content Display Options**
|
||||
- Article intro/full text display controls
|
||||
- Category layout variations (grid, list, masonry, cards)
|
||||
- Featured content sections
|
||||
- Related articles display options
|
||||
- Author bio box styling
|
||||
|
||||
- **Responsive Design Improvements**
|
||||
- Mobile-first navigation patterns
|
||||
- Tablet-specific layout controls
|
||||
- Responsive image sizing options
|
||||
- Mobile header variations
|
||||
- Touch-friendly interface elements
|
||||
|
||||
- **Template Integration Features**
|
||||
- Enhanced VirtueMart template overrides
|
||||
- Contact form styling variations
|
||||
- Search result layout options
|
||||
- Error page customization
|
||||
- Archive page templates
|
||||
|
||||
**Template Infrastructure**:
|
||||
- Joomla 6.x template compatibility (if released)
|
||||
- PHP 8.2+ support
|
||||
- Template child theme support
|
||||
- Template preset import/export functionality
|
||||
|
||||
---
|
||||
|
||||
#### v06.00.00 (Q4 2029) - Template Extensions & Advanced Features
|
||||
**Status**: Planned
|
||||
**Target Release**: December 2029
|
||||
|
||||
**Major Template Features**:
|
||||
- **Template Marketplace & Extensions**
|
||||
- Template addon system for modular features
|
||||
- Community-contributed template extensions
|
||||
- Template preset marketplace
|
||||
- Style pack distribution system
|
||||
- Template component library
|
||||
|
||||
- **Advanced Module System**
|
||||
- Custom module chrome options
|
||||
- Module animation effects
|
||||
- Module visibility controls (scroll, time-based)
|
||||
- Module group management
|
||||
- Module style inheritance
|
||||
|
||||
- **Enhanced Media Handling**
|
||||
- Background image options per page/section
|
||||
- Image overlay controls
|
||||
- Parallax scrolling effects
|
||||
- Video background support
|
||||
- Gallery template variations
|
||||
|
||||
- **Template Branding Options**
|
||||
- Multiple logo upload (standard, retina, mobile)
|
||||
- Favicon and app icon management
|
||||
- Custom loading screen/animations
|
||||
- Watermark options
|
||||
- Brand color scheme generator
|
||||
|
||||
- **Advanced Header/Footer**
|
||||
- Multiple header layout presets
|
||||
- Sticky header variations and behaviors
|
||||
- Header transparency controls
|
||||
- Footer widget areas expansion
|
||||
- Floating action buttons
|
||||
|
||||
- **Content Enhancement Features**
|
||||
- Reading progress indicator
|
||||
- Social sharing buttons (template-integrated)
|
||||
- Print-friendly styles
|
||||
- Reading time estimation display
|
||||
- Content table enhancements
|
||||
|
||||
- **Template SEO Features**
|
||||
- Schema markup templates for common types
|
||||
- Open Graph tag management
|
||||
- Twitter Card support
|
||||
- Breadcrumb schema integration
|
||||
- Meta tag template controls
|
||||
|
||||
**Template Infrastructure**:
|
||||
- Template versioning system
|
||||
- Template backup/restore functionality
|
||||
- Template A/B testing support
|
||||
- Multi-language template variations
|
||||
- Template documentation generator
|
||||
|
||||
---
|
||||
|
||||
#### v07.00.00 (Q4 2030) - Modern Template Standards & Enhancements
|
||||
**Status**: Planned
|
||||
**Target Release**: December 2030
|
||||
|
||||
**Major Template Features**:
|
||||
- **Modern CSS Features**
|
||||
- CSS Grid layout system integration
|
||||
- CSS Container Queries support
|
||||
- CSS Cascade Layers implementation (layered style priority system)
|
||||
- Custom properties (CSS variables) UI
|
||||
- Modern filter and backdrop effects
|
||||
|
||||
- **Progressive Template Features**
|
||||
- Offline-capable template assets
|
||||
- Service worker template integration
|
||||
- App manifest generation
|
||||
- Install to home screen support
|
||||
- Template asset preloading strategies
|
||||
|
||||
- **Animation & Interaction**
|
||||
- Scroll-triggered animations
|
||||
- Hover effect library
|
||||
- Page transition effects
|
||||
- Micro-interactions for UI elements
|
||||
- Loading animation options
|
||||
|
||||
- **Advanced Responsive Features**
|
||||
- Container-based responsive design
|
||||
- Element visibility by viewport
|
||||
- Responsive navigation patterns library
|
||||
- Mobile-optimized interactions
|
||||
- Adaptive image loading
|
||||
|
||||
- **Template Accessibility Features**
|
||||
- Focus indicators customization
|
||||
- Reduced motion preferences support
|
||||
- High contrast mode automation
|
||||
- Keyboard navigation patterns
|
||||
- ARIA live regions for dynamic content
|
||||
|
||||
- **Content Presentation**
|
||||
- Advanced blockquote styles
|
||||
- Code snippet highlighting themes
|
||||
- Table styling variations
|
||||
- List styling options
|
||||
- Custom content block templates
|
||||
|
||||
- **Template Performance**
|
||||
- Resource hints (preconnect, prefetch)
|
||||
- Optimal asset delivery strategies
|
||||
- Image format optimization (AVIF support)
|
||||
- Font loading optimization
|
||||
- Template metrics dashboard
|
||||
|
||||
**Template Infrastructure**:
|
||||
- Template pattern library
|
||||
- Design token system
|
||||
- Template component documentation
|
||||
- Automated template testing suite
|
||||
- Template performance monitoring
|
||||
|
||||
---
|
||||
|
||||
#### v08.00.00 (Q4 2031) - Next-Generation Template Features
|
||||
**Status**: Conceptual
|
||||
**Target Release**: December 2031
|
||||
|
||||
**Major Template Features**:
|
||||
- **Advanced Layout Systems**
|
||||
- Subgrid support for complex layouts
|
||||
- Multi-column layout variations
|
||||
- Asymmetric grid systems
|
||||
- Dynamic layout switching
|
||||
- Layout constraint system
|
||||
|
||||
- **Enhanced Visual Customization**
|
||||
- Real-time style editor
|
||||
- Template style variations manager
|
||||
- Custom CSS injection with validation
|
||||
- Style inheritance and override system
|
||||
- Visual design tokens editor
|
||||
|
||||
- **Template Component Library**
|
||||
- Comprehensive UI component set
|
||||
- Reusable template blocks
|
||||
- Component variation system
|
||||
- Template snippet library
|
||||
- Pattern library integration
|
||||
|
||||
- **Advanced Typography System**
|
||||
- Variable font support
|
||||
- Advanced typographic scales
|
||||
- Font pairing recommendations
|
||||
- Fluid typography system
|
||||
- Custom font fallback chains
|
||||
|
||||
- **Template Integration Features**
|
||||
- Enhanced component overrides
|
||||
- Template hooks system
|
||||
- Event-based template modifications
|
||||
- Custom field rendering templates
|
||||
- Module position API enhancements
|
||||
|
||||
- **Responsive & Adaptive Design**
|
||||
- Advanced breakpoint management
|
||||
- Element-specific responsive controls
|
||||
- Adaptive images with art direction
|
||||
- Responsive typography system
|
||||
- Context-aware component rendering
|
||||
|
||||
- **Template Ecosystem**
|
||||
- Child template framework
|
||||
- Template derivative system
|
||||
- Community template marketplace
|
||||
- Template rating and review system
|
||||
- Professional template support network
|
||||
|
||||
- **Template Quality & Maintenance**
|
||||
- Automated accessibility testing
|
||||
- Template performance auditing
|
||||
- Code quality monitoring
|
||||
- Update notification system
|
||||
- Template health dashboard
|
||||
|
||||
**Template Infrastructure**:
|
||||
- Template API for extensibility
|
||||
- Template package manager
|
||||
- Template development CLI tools
|
||||
- Template migration utilities
|
||||
- Comprehensive template documentation system
|
||||
|
||||
---
|
||||
|
||||
## Current Release (v03.06.03)
|
||||
|
||||
### System Requirements
|
||||
- **Joomla**: 4.4.x or 5.x
|
||||
- **PHP**: 8.0+
|
||||
- **Database**: MySQL/MariaDB compatible
|
||||
|
||||
### Architecture
|
||||
- **Base Template**: Joomla Cassiopeia
|
||||
- **Enhancement Layer**: Non-invasive overrides
|
||||
- **Asset Management**: Joomla Web Asset Manager (WAM)
|
||||
- **Frontend Framework**: Bootstrap 5
|
||||
- **Icon Library**: Font Awesome 7 Free
|
||||
|
||||
---
|
||||
|
||||
## Implemented Features
|
||||
|
||||
### 🎨 Theming & Visual Design
|
||||
|
||||
#### Color Palette System
|
||||
- **3 Built-in Palettes**: Standard, Alternative, Custom
|
||||
- **Dual Mode Support**: Separate light and dark configurations
|
||||
- **Custom Palettes**: User-definable via `colors_custom.css`
|
||||
- **Location**: `src/media/css/colors/{light|dark}/`
|
||||
|
||||
#### Dark Mode System
|
||||
- **Toggle Controls**: Switch (Light↔Dark) or Radios (Light/Dark/System)
|
||||
- **Default Mode**: Admin-configurable (system, light, or dark)
|
||||
- **Persistence**: localStorage for user preferences
|
||||
- **Auto-Detection**: Optional system preference detection
|
||||
- **Meta Tags**: `color-scheme` and `theme-color` support
|
||||
- **ARIA Bridge**: Bootstrap ARIA compatibility
|
||||
|
||||
#### Typography
|
||||
- **Font Schemes**:
|
||||
- Local: Roboto
|
||||
- Web (Google Fonts): Fira Sans, Roboto + Noto Sans
|
||||
- **Admin-Configurable**: Template settings dropdown
|
||||
|
||||
#### Branding
|
||||
- **Logo Support**: Custom logo upload
|
||||
- **Site Title**: Text-based branding option
|
||||
- **Site Description**: Tagline/subtitle field
|
||||
- **Font Awesome Kit**: Optional custom kit integration
|
||||
|
||||
### 📐 Layout & Structure
|
||||
|
||||
#### Module Positions (23 Total)
|
||||
**Header Area**:
|
||||
- topbar, below-topbar, below-logo, menu, search, banner
|
||||
|
||||
**Content Area**:
|
||||
- top-a, top-b, main-top, main-bottom, breadcrumbs
|
||||
- sidebar-left, sidebar-right
|
||||
|
||||
**Footer Area**:
|
||||
- bottom-a, bottom-b, footer-menu, footer
|
||||
|
||||
**Special**:
|
||||
- debug, offline-header, offline, offline-footer
|
||||
- drawer-left, drawer-right
|
||||
|
||||
#### Layout Options
|
||||
- **Container Type**: Fluid or Static
|
||||
- **Sticky Header**: Optional fixed navigation
|
||||
- **Back-to-Top Button**: Scrollable page support
|
||||
|
||||
### 📝 Content Features
|
||||
|
||||
#### Table of Contents (TOC)
|
||||
- **Automatic Generation**: From article headings
|
||||
- **Placement Options**: `toc-left` or `toc-right` layouts
|
||||
- **Article Integration**: Via Options → Layout dropdown
|
||||
- **Responsive**: Mobile-friendly sidebar placement
|
||||
|
||||
#### Article Layouts
|
||||
- **Default**: Standard Cassiopeia layout
|
||||
- **TOC Variants**: Left-sidebar or right-sidebar TOC
|
||||
- **Custom Overrides**: Located in `html/com_content/article/`
|
||||
|
||||
### 📊 Analytics & Tracking
|
||||
|
||||
#### Google Tag Manager (GTM)
|
||||
- **Enable/Disable**: Admin toggle
|
||||
- **Container ID**: Template parameter field
|
||||
- **Implementation**: Head and body script injection
|
||||
- **GDPR-Ready**: Configurable consent defaults
|
||||
|
||||
#### Google Analytics 4 (GA4)
|
||||
- **Enable/Disable**: Admin toggle
|
||||
- **Property ID**: Template parameter field
|
||||
- **Universal Analytics Fallback**: Legacy UA support
|
||||
- **Privacy-First**: Conditional loading based on settings
|
||||
|
||||
### 🎛️ Customization & Developer Tools
|
||||
|
||||
#### Custom Code Injection
|
||||
- **Head Start**: Custom HTML/JS before `</head>`
|
||||
- **Head End**: Custom HTML/JS at end of `<head>`
|
||||
- **Raw HTML**: Unfiltered code injection for advanced users
|
||||
|
||||
#### Drawer System
|
||||
- **Left/Right Drawers**: Offcanvas menu areas
|
||||
- **Icon Customization**: Font Awesome icon selection
|
||||
- **Default Icons**:
|
||||
- Left: `fa-solid fa-chevron-right`
|
||||
- Right: `fa-solid fa-chevron-left`
|
||||
|
||||
#### Asset Management
|
||||
- **Joomla WAM**: Complete asset registry in `joomla.asset.json`
|
||||
- **Development/Production Modes**: Minified and unminified assets
|
||||
- **Dependency Management**: Automatic script/style loading
|
||||
|
||||
### 🏗️ Template Overrides
|
||||
|
||||
#### Component Overrides
|
||||
**Content (com_content)**:
|
||||
- Article layouts (default, toc-left, toc-right)
|
||||
- Category layouts (blog, list)
|
||||
- Featured articles
|
||||
|
||||
**Contact (com_contact)**:
|
||||
- Contact form layouts
|
||||
|
||||
**Engage (com_engage)**:
|
||||
- Comment system integration
|
||||
|
||||
#### Module Overrides
|
||||
**Menu (mod_menu)**:
|
||||
- Metis dropdown menu
|
||||
- Offcanvas navigation
|
||||
|
||||
**VirtueMart**:
|
||||
- Product display (`mod_virtuemart_product`)
|
||||
- Shopping cart (`mod_virtuemart_cart`)
|
||||
- Manufacturer display (`mod_virtuemart_manufacturer`)
|
||||
- Category display (`mod_virtuemart_category`)
|
||||
- Currency selector (`mod_virtuemart_currencies`)
|
||||
|
||||
**Other Modules**:
|
||||
- Custom HTML (`mod_custom`)
|
||||
- GABble social integration (`mod_gabble`)
|
||||
|
||||
**Membership System (OS Membership)**:
|
||||
- Plan layouts (default, pricing tables)
|
||||
- Member management interfaces
|
||||
|
||||
### 🔧 Configuration Parameters
|
||||
|
||||
#### Theme Tab
|
||||
**General**:
|
||||
- `theme_enabled` - Enable/disable theme system
|
||||
- `theme_control_type` - Toggle UI type (switch/radios/none)
|
||||
- `theme_default_choice` - Default mode (system/light/dark)
|
||||
- `theme_auto_dark` - Auto-detect system preference
|
||||
- `theme_meta_color_scheme` - Inject `color-scheme` meta tag
|
||||
- `theme_meta_theme_color` - Inject `theme-color` meta tag
|
||||
- `theme_bridge_bs_aria` - Bootstrap ARIA compatibility
|
||||
|
||||
**Variables & Palettes**:
|
||||
- `colorLightName` - Light mode color scheme
|
||||
- `colorDarkName` - Dark mode color scheme
|
||||
|
||||
**Typography**:
|
||||
- `useFontScheme` - Font selection (local/web)
|
||||
|
||||
**Branding & Icons**:
|
||||
- `brand` - Show/hide branding
|
||||
- `logoFile` - Logo upload path
|
||||
- `siteTitle` - Site title text
|
||||
- `siteDescription` - Site tagline
|
||||
- `fA6KitCode` - Font Awesome kit code
|
||||
|
||||
**Header & Navigation**:
|
||||
- `stickyHeader` - Fixed navigation
|
||||
- `backTop` - Back-to-top button
|
||||
|
||||
**Toggle UI**:
|
||||
- `theme_fab_enabled` - Floating action button for theme toggle
|
||||
- `theme_fab_pos` - FAB position (br/bl/tr/tl)
|
||||
|
||||
#### Google Tab
|
||||
- `googletagmanager` - Enable GTM
|
||||
- `googletagmanagerid` - GTM container ID
|
||||
- `googleanalytics` - Enable GA4
|
||||
- `googleanalyticsid` - GA4 property ID
|
||||
|
||||
#### Custom Code Tab
|
||||
- `custom_head_start` - Custom code at head start
|
||||
- `custom_head_end` - Custom code at head end
|
||||
|
||||
#### Drawers Tab
|
||||
- `drawerLeftIcon` - Left drawer icon (Font Awesome class)
|
||||
- `drawerRightIcon` - Right drawer icon (Font Awesome class)
|
||||
|
||||
#### Advanced Tab
|
||||
- `fluidContainer` - Container layout (static/fluid)
|
||||
|
||||
### 🛠️ Development Tools
|
||||
|
||||
#### Quality Assurance
|
||||
- **Codeception**: Automated testing framework
|
||||
- **PHPStan**: Static analysis (level 8+)
|
||||
- **PHPCS**: Code style validation (PSR-12)
|
||||
- **PHPCompatibility**: PHP 8.0+ compatibility checks
|
||||
|
||||
#### CI/CD Workflows
|
||||
- **Dependency Review**: Vulnerability scanning
|
||||
- **Standards Compliance**: MokoStandards validation
|
||||
- **CodeQL**: Security analysis (GitHub default)
|
||||
- **Dependabot**: Automated dependency updates
|
||||
|
||||
#### Documentation
|
||||
- **Quick Start**: 5-minute developer setup
|
||||
- **Workflow Guide**: Git strategy, branching, releases
|
||||
- **Joomla Development**: Testing, packaging, multi-version support
|
||||
|
||||
---
|
||||
|
||||
## Planned Features
|
||||
|
||||
### 🚧 In Development
|
||||
|
||||
#### Soft Offline Mode (v03.07.00 - Planned)
|
||||
**Status**: Planned for v03.07.00
|
||||
**Priority**: High
|
||||
**Description**: Keep selected categories accessible during site maintenance mode with persistent links to essential pages
|
||||
|
||||
**Use Cases**:
|
||||
- Legal documents remain viewable during downtime
|
||||
- Policy pages accessible for compliance requirements
|
||||
- Terms of service always available to users
|
||||
- Privacy policy accessible at all times
|
||||
- Essential public information during maintenance
|
||||
|
||||
**Technical Specifications**:
|
||||
- **Configuration Method**: Template parameters in `templateDetails.xml`
|
||||
- **Category Access**: Category IDs stored as comma-separated values
|
||||
- **Persistent Links**: Direct article/menu item links always visible
|
||||
- **Access Control**: Check in `offline.php` template file
|
||||
- **Content Rendering**: Use Joomla's content component to fetch articles
|
||||
- **Security**: Maintain proper access levels and permissions
|
||||
|
||||
**Implementation Plan**:
|
||||
1. Add category selection field to template parameters
|
||||
2. Add persistent link configuration (Terms of Service, Privacy Policy, etc.)
|
||||
3. Modify `offline.php` to check for allowed categories
|
||||
4. Add persistent link display in offline mode header/footer
|
||||
5. Implement category content fetching during offline mode
|
||||
6. Add styling for offline mode category display and persistent links
|
||||
7. Test with various category and link configurations
|
||||
8. Document admin configuration steps
|
||||
|
||||
**Configuration Interface**:
|
||||
- **Category Field Type**: Category multiselect in template settings
|
||||
- **Label**: "Categories Accessible During Offline Mode"
|
||||
- **Default**: None (all content hidden by default)
|
||||
- **Persistent Links**: Text fields for essential always-available links
|
||||
- **Terms of Service URL**: Direct link to TOS article/page
|
||||
- **Privacy Policy URL**: Direct link to privacy policy
|
||||
- **Contact URL**: Optional contact page link
|
||||
- **Custom Link 1-3**: Additional persistent links if needed
|
||||
- **Admin Path**: System → Site Templates → MokoOnyx → Advanced → Offline Mode Settings
|
||||
|
||||
**Persistent Links Feature**:
|
||||
- **Display Location**: Footer of offline page
|
||||
- **Styling**: Clearly visible, accessible links
|
||||
- **Format**: "Terms of Service | Privacy Policy | Contact"
|
||||
- **Behavior**: Links bypass offline mode restrictions
|
||||
- **Validation**: Check if URLs are valid Joomla routes
|
||||
|
||||
**Benefits**:
|
||||
- ✅ Compliance: Keep legal pages accessible
|
||||
- ✅ Transparency: Users can access essential information
|
||||
- ✅ Flexibility: Admin control over which categories remain visible
|
||||
- ✅ Security: Respects Joomla access levels
|
||||
- ✅ Legal Protection: Terms of Service always accessible
|
||||
- ✅ User Trust: Privacy policy always available
|
||||
|
||||
**Milestone**: Target release v03.07.00 (Q2 2026)
|
||||
|
||||
#### TODO Tracking System
|
||||
**Status**: Mentioned in CHANGELOG (v03.05.00)
|
||||
**Description**: Separate TODO tracking file
|
||||
**Purpose**: Centralized issue and feature tracking outside changelog
|
||||
|
||||
### 🔮 Future Enhancements
|
||||
|
||||
#### Development Mode (Commented Out)
|
||||
**Status**: Code exists but disabled
|
||||
**Location**: `templateDetails.xml` line 91
|
||||
**Description**: Comprehensive development mode toggle
|
||||
**Potential Features**:
|
||||
- Unminified asset loading
|
||||
- Debug output
|
||||
- Performance profiling
|
||||
- Template cache bypass
|
||||
|
||||
#### Potential Features (Community Requested)
|
||||
*Note: These are conceptual and not yet officially planned*
|
||||
|
||||
**Enhanced Accessibility**:
|
||||
- WCAG 2.1 AAA compliance mode
|
||||
- High-contrast themes
|
||||
- Screen reader optimizations
|
||||
- Keyboard navigation improvements
|
||||
|
||||
**Template Layout Features**:
|
||||
- Advanced responsive grid layouts
|
||||
- Multiple column variations
|
||||
- Custom module position system
|
||||
- Layout preset library
|
||||
|
||||
**Template Styling Features**:
|
||||
- Extended color palette management
|
||||
- Custom font upload support
|
||||
- Typography scale controls
|
||||
- Visual style editor
|
||||
|
||||
---
|
||||
|
||||
## Development Priorities
|
||||
|
||||
### Immediate Focus (v03.x - 2026)
|
||||
1. **Bootstrap TOC Integration**: Complete and document v1.0.1 implementation ✅
|
||||
2. **Soft Offline Mode**: Implement category-based offline access (Target: v03.07.00)
|
||||
3. **TODO Tracking System**: Implement separate file for issue tracking
|
||||
4. **Security Updates**: Maintain Dependabot and CodeQL scans
|
||||
5. **Documentation**: Keep docs synchronized with features
|
||||
6. **Bug Fixes**: Address reported issues and edge cases
|
||||
|
||||
### v04.00.00 Priorities (2027) - Template Foundation
|
||||
1. **WCAG 2.1 AA Compliance**: Full template accessibility audit and implementation
|
||||
2. **Template Performance**: Critical CSS, lazy loading, WebP support
|
||||
3. **Layout System**: Enhanced responsive grid and module positions
|
||||
4. **Development Mode**: Enable comprehensive template developer tools
|
||||
|
||||
### v05.00.00 Priorities (2028) - Template Customization
|
||||
1. **Layout Builder**: Template-based page layout system
|
||||
2. **Styling System**: Extended color palettes and CSS variable management
|
||||
3. **Template Components**: Enhanced header, footer, and menu variations
|
||||
4. **Responsive Design**: Mobile-first navigation and layout improvements
|
||||
|
||||
### v06.00.00 Priorities (2029) - Template Extensions
|
||||
1. **Template Marketplace**: Addon system and community extensions
|
||||
2. **Module System**: Advanced module chrome and animation options
|
||||
3. **Media Handling**: Background images, parallax, video backgrounds
|
||||
4. **Template SEO**: Schema markup templates and meta tag controls
|
||||
|
||||
### v07.00.00+ Priorities (2030+) - Modern Standards
|
||||
1. **Modern CSS**: Grid, Container Queries, Cascade Layers
|
||||
2. **Progressive Template**: Offline-capable assets and PWA features
|
||||
3. **Animation System**: Scroll-triggered effects and micro-interactions
|
||||
4. **Template Performance**: Advanced optimization and monitoring
|
||||
|
||||
---
|
||||
|
||||
## Long-term Vision
|
||||
|
||||
### Mission Statement
|
||||
MokoOnyx aims to be the **most developer-friendly, user-customizable, and standards-compliant Joomla template** while maintaining minimal core overrides for maximum upgrade compatibility.
|
||||
|
||||
### Core Principles
|
||||
1. **Non-Invasive**: Minimal Cassiopeia overrides
|
||||
2. **Standards-First**: MokoStandards compliance
|
||||
3. **Accessibility**: WCAG 2.1 compliance
|
||||
4. **Performance**: Fast, optimized delivery
|
||||
5. **Developer Experience**: Clear docs, easy setup, powerful tools
|
||||
6. **Template-Focused**: Pure template features without complex external dependencies
|
||||
|
||||
### 5-Year Strategic Roadmap (Template Features)
|
||||
|
||||
#### 2027 (v04.00.00) - Accessibility & Performance
|
||||
- Achieve WCAG 2.1 AA compliance for all template elements
|
||||
- Implement critical template performance optimizations
|
||||
- Enhance template layout system with flexible grids
|
||||
- Enable comprehensive development mode for template developers
|
||||
|
||||
#### 2028 (v05.00.00) - Layouts & Customization
|
||||
- Launch template-based layout builder system
|
||||
- Deploy extended styling and customization options
|
||||
- Enhance template component variations (headers, footers, menus)
|
||||
- Improve responsive design patterns for all devices
|
||||
|
||||
#### 2029 (v06.00.00) - Extensions & Enhancements
|
||||
- Introduce template addon and extension system
|
||||
- Launch template preset marketplace
|
||||
- Deploy advanced module styling and animation features
|
||||
- Implement comprehensive template SEO controls
|
||||
|
||||
#### 2030 (v07.00.00) - Modern Standards
|
||||
- Adopt modern CSS standards (Grid, Container Queries, Cascade Layers)
|
||||
- Implement progressive template features (PWA support)
|
||||
- Deploy advanced animation and interaction system
|
||||
- Enhance template performance monitoring and optimization
|
||||
|
||||
#### 2031 (v08.00.00) - Next-Generation Template
|
||||
- Advanced layout systems with subgrid support
|
||||
- Comprehensive template component library
|
||||
- Enhanced visual customization tools
|
||||
- Template ecosystem with child themes and derivatives
|
||||
|
||||
---
|
||||
|
||||
## External Resources
|
||||
|
||||
### Official Links
|
||||
- **Full Roadmap**: [https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap](https://mokoconsulting.tech/support/joomla-cms/mokoonyx-roadmap)
|
||||
- **Repository**: [https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia](https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia)
|
||||
- **Issue Tracker**: [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia/issues)
|
||||
- **Changelog**: [CHANGELOG.md](../CHANGELOG.md)
|
||||
|
||||
### Community
|
||||
- **Email Support**: hello@mokoconsulting.tech
|
||||
- **Contributing**: [CONTRIBUTING.md](../CONTRIBUTING.md)
|
||||
- **Code of Conduct**: [CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)
|
||||
|
||||
### Documentation
|
||||
- **Quick Start**: [docs/QUICK_START.md](./QUICK_START.md)
|
||||
- **Workflow Guide**: [docs/WORKFLOW_GUIDE.md](./WORKFLOW_GUIDE.md)
|
||||
- **Joomla Development**: [docs/JOOMLA_DEVELOPMENT.md](./JOOMLA_DEVELOPMENT.md)
|
||||
- **Main README**: [README.md](../README.md)
|
||||
|
||||
---
|
||||
|
||||
## Contributing to the Roadmap
|
||||
|
||||
Have ideas for future features? We welcome community input!
|
||||
|
||||
**How to Suggest Features**:
|
||||
1. Check the [GitHub Issues](https://git.mokoconsulting.tech/MokoConsulting/moko-cassiopeia/issues) for existing requests
|
||||
2. Open a new issue with the `enhancement` label
|
||||
3. Provide clear use cases and benefits
|
||||
4. Engage in community discussion
|
||||
|
||||
**Feature Evaluation Criteria**:
|
||||
- Alignment with core principles
|
||||
- User demand and use cases
|
||||
- Technical feasibility
|
||||
- Maintenance burden
|
||||
- Performance impact
|
||||
- Security implications
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* Document: docs/ROADMAP.md
|
||||
* Repository: [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* Path: /docs/ROADMAP.md
|
||||
* Owner: Moko Consulting
|
||||
* Version: 03.06.03
|
||||
* Status: Active
|
||||
* Effective Date: 2026-01-30
|
||||
* Classification: Public Open Source Documentation
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ----------------------------------------------------- | --------------- |
|
||||
| 2026-01-27 | Initial version-specific roadmap generated from codebase scan. | GitHub Copilot |
|
||||
| 2026-01-27 | Added 5-year future roadmap with annual major version releases (v04-v08). | GitHub Copilot |
|
||||
| 2026-01-27 | Refocused roadmap to concentrate on template-oriented features only. | GitHub Copilot |
|
||||
459
docs/WORKFLOW_GUIDE.md
Normal file
459
docs/WORKFLOW_GUIDE.md
Normal file
@@ -0,0 +1,459 @@
|
||||
# Workflow Guide - Moko Cassiopeia
|
||||
|
||||
Quick reference guide for GitHub Actions workflows and common development tasks.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Workflow Quick Reference](#workflow-quick-reference)
|
||||
- [Common Development Tasks](#common-development-tasks)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Best Practices](#best-practices)
|
||||
|
||||
## Overview
|
||||
|
||||
This repository uses GitHub Actions for continuous integration, testing, quality checks, and deployment. All workflows are located in `.github/workflows/`.
|
||||
|
||||
### Workflow Execution Model
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Code Changes │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ CI Pipeline │ ← Validation, Testing, Quality
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Version Branch │ ← Create dev/X.Y.Z branch
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Release Pipeline│ ← dev → rc → version → main
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Distribution │ ← ZIP package + GitHub Release
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Workflow Quick Reference
|
||||
|
||||
### Continuous Integration (ci.yml)
|
||||
|
||||
**Trigger:** Automatic on push/PR to main, dev/*, rc/*, version/* branches
|
||||
|
||||
**Purpose:** Validates code quality and repository structure
|
||||
|
||||
**What it does:**
|
||||
- ✅ Validates Joomla manifest XML
|
||||
- ✅ Checks XML well-formedness
|
||||
- ✅ Validates GitHub Actions workflows
|
||||
- ✅ Runs PHP syntax checks
|
||||
- ✅ Checks for secrets in code
|
||||
- ✅ Validates license headers
|
||||
- ✅ Verifies version alignment
|
||||
|
||||
**When to check:** After every commit
|
||||
|
||||
**How to view results:**
|
||||
```bash
|
||||
# Via GitHub CLI
|
||||
gh run list --workflow=ci.yml --limit 5
|
||||
gh run view <run-id> --log
|
||||
```
|
||||
|
||||
### PHP Quality Checks (php_quality.yml)
|
||||
|
||||
**Trigger:** Automatic on push/PR to main, dev/*, rc/*, version/* branches
|
||||
|
||||
**Purpose:** Ensures PHP code quality and compatibility
|
||||
|
||||
**What it does:**
|
||||
- 🔍 PHPStan static analysis (level 5)
|
||||
- 📏 PHP_CodeSniffer with PSR-12 standards
|
||||
- ✔️ PHP 8.0+ compatibility checks
|
||||
|
||||
**Matrix:** PHP 8.0, 8.1, 8.2, 8.3
|
||||
|
||||
**When to check:** Before committing PHP changes
|
||||
|
||||
**How to run locally:**
|
||||
```bash
|
||||
# Install tools
|
||||
composer global require "squizlabs/php_codesniffer:^3.0" --with-all-dependencies
|
||||
composer global require "phpstan/phpstan:^1.0" --with-all-dependencies
|
||||
|
||||
# Run checks
|
||||
phpcs --standard=phpcs.xml src/
|
||||
phpstan analyse --configuration=phpstan.neon
|
||||
```
|
||||
|
||||
### Joomla Testing (joomla_testing.yml)
|
||||
|
||||
**Trigger:** Automatic on push/PR to main, dev/*, rc/* branches
|
||||
|
||||
**Purpose:** Tests template compatibility with Joomla versions
|
||||
|
||||
**What it does:**
|
||||
- 📦 Downloads and installs Joomla (4.4, 5.0, 5.1)
|
||||
- 🔧 Installs template into Joomla
|
||||
- ✅ Validates template installation
|
||||
- 🧪 Runs Codeception tests
|
||||
|
||||
**Matrix:** Joomla 4.4/5.0/5.1 × PHP 8.0/8.1/8.2/8.3
|
||||
|
||||
**When to check:** Before releasing new versions
|
||||
|
||||
**How to test locally:**
|
||||
```bash
|
||||
# See docs/JOOMLA_DEVELOPMENT.md for local testing setup
|
||||
codecept run
|
||||
```
|
||||
|
||||
### Version Branch Creation (version_branch.yml)
|
||||
|
||||
**Trigger:** Manual workflow dispatch
|
||||
|
||||
**Purpose:** Creates a new version branch and bumps version numbers
|
||||
|
||||
**What it does:**
|
||||
- 🏷️ Creates dev/*, rc/*, or version/* branch
|
||||
- 📝 Updates version in all files
|
||||
- 📅 Updates manifest dates
|
||||
- 📋 Moves CHANGELOG unreleased to version
|
||||
- ✅ Validates version hierarchy
|
||||
|
||||
**When to use:** Starting work on a new version
|
||||
|
||||
**How to run:**
|
||||
1. Go to Actions → Create version branch
|
||||
2. Click "Run workflow"
|
||||
3. Enter version (e.g., 03.06.00)
|
||||
4. Select branch prefix (dev/, rc/, or version/)
|
||||
5. Click "Run workflow"
|
||||
|
||||
**Example:**
|
||||
```yaml
|
||||
new_version: 03.06.00
|
||||
branch_prefix: dev/
|
||||
version_text: beta
|
||||
```
|
||||
|
||||
### Release Pipeline (release_pipeline.yml)
|
||||
|
||||
**Trigger:** Manual workflow dispatch or release event
|
||||
|
||||
**Purpose:** Promotes branches through release stages and creates distributions
|
||||
|
||||
**What it does:**
|
||||
- 🔄 Promotes branches: dev → rc → version → main
|
||||
- 📅 Normalizes dates in manifest and CHANGELOG
|
||||
- 📦 Builds distributable ZIP package
|
||||
- 🚀 Uploads to SFTP server
|
||||
- 🏷️ Creates Git tag
|
||||
- 📋 Creates GitHub Release
|
||||
- 🔒 Attests build provenance
|
||||
|
||||
**When to use:** Promoting a version through release stages
|
||||
|
||||
**How to run:**
|
||||
1. Go to Actions → Release Pipeline
|
||||
2. Click "Run workflow"
|
||||
3. Select classification (auto/rc/stable)
|
||||
4. Click "Run workflow"
|
||||
|
||||
**Release flow:**
|
||||
```
|
||||
dev/X.Y.Z → rc/X.Y.Z → version/X.Y.Z → main
|
||||
(dev) (RC) (stable) (production)
|
||||
```
|
||||
|
||||
### Deploy to Staging (deploy_staging.yml)
|
||||
|
||||
**Trigger:** Manual workflow dispatch
|
||||
|
||||
**Purpose:** Deploys template to staging/development environments
|
||||
|
||||
**What it does:**
|
||||
- ✅ Validates deployment prerequisites
|
||||
- 📦 Builds deployment package
|
||||
- 🚀 Uploads via SFTP to environment
|
||||
- 📝 Creates deployment summary
|
||||
|
||||
**When to use:** Testing in staging before production release
|
||||
|
||||
**How to run:**
|
||||
1. Go to Actions → Deploy to Staging
|
||||
2. Click "Run workflow"
|
||||
3. Select environment (staging/development/preview)
|
||||
4. Optionally specify version
|
||||
5. Click "Run workflow"
|
||||
|
||||
**Required secrets:**
|
||||
- `STAGING_HOST` - SFTP hostname
|
||||
- `STAGING_USER` - SFTP username
|
||||
- `STAGING_KEY` - SSH private key (or `STAGING_PASSWORD`)
|
||||
- `STAGING_PATH` - Remote deployment path
|
||||
|
||||
### Repository Health (repo_health.yml)
|
||||
|
||||
**Trigger:** Manual workflow dispatch (admin only)
|
||||
|
||||
**Purpose:** Comprehensive repository health and configuration checks
|
||||
|
||||
**What it does:**
|
||||
- 🔐 Validates release configuration
|
||||
- 🌐 Tests SFTP connectivity
|
||||
- 📂 Checks scripts governance
|
||||
- 📄 Validates required artifacts
|
||||
- 🔍 Extended checks (SPDX, ShellCheck, etc.)
|
||||
|
||||
**When to use:** Before major releases or when debugging deployment issues
|
||||
|
||||
**How to run:**
|
||||
1. Go to Actions → Repo Health
|
||||
2. Click "Run workflow"
|
||||
3. Select profile (all/release/repo)
|
||||
4. Click "Run workflow"
|
||||
|
||||
**Profiles:**
|
||||
- `all` - Run all checks
|
||||
- `release` - Release configuration and SFTP only
|
||||
- `scripts` - Scripts governance only
|
||||
- `repo` - Repository health only
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Starting a New Feature
|
||||
|
||||
```bash
|
||||
# 1. Create a new version branch via GitHub Actions
|
||||
# Actions → Create version branch → dev/X.Y.Z
|
||||
|
||||
# 2. Clone and checkout the new branch
|
||||
git fetch origin
|
||||
git checkout dev/X.Y.Z
|
||||
|
||||
# 3. Make your changes
|
||||
vim src/index.php
|
||||
|
||||
# 4. Validate locally
|
||||
make validate-required
|
||||
|
||||
# 5. Commit and push
|
||||
git add -A
|
||||
git commit -m "feat: add new feature"
|
||||
git push origin dev/X.Y.Z
|
||||
```
|
||||
|
||||
### Running All Validations Locally
|
||||
|
||||
```bash
|
||||
# Run comprehensive validation suite
|
||||
make validate-required
|
||||
|
||||
# Run quality checks
|
||||
make quality
|
||||
```
|
||||
|
||||
### Creating a Release Package
|
||||
|
||||
```bash
|
||||
# Package with auto-detected version
|
||||
```bash
|
||||
# Package with auto-detected version
|
||||
make package
|
||||
|
||||
# Verify package contents
|
||||
unzip -l dist/moko-cassiopeia-*.zip
|
||||
```
|
||||
|
||||
### Updating Version Numbers
|
||||
|
||||
```bash
|
||||
# Via GitHub Actions (recommended)
|
||||
# Actions → Release Management workflow
|
||||
```
|
||||
|
||||
### Updating CHANGELOG
|
||||
|
||||
Update CHANGELOG.md manually or via pull request following the existing format.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### CI Failures
|
||||
|
||||
#### PHP Syntax Errors
|
||||
|
||||
```bash
|
||||
# Check specific file
|
||||
php -l src/index.php
|
||||
|
||||
# Run validation
|
||||
make validate-required
|
||||
```
|
||||
|
||||
#### Manifest Validation Failed
|
||||
|
||||
```bash
|
||||
# Validate manifest and XML files
|
||||
make validate-required
|
||||
```
|
||||
|
||||
#### Version Alignment Issues
|
||||
|
||||
```bash
|
||||
# Check version consistency
|
||||
make validate-required
|
||||
```
|
||||
|
||||
### Workflow Failures
|
||||
|
||||
#### "Branch already exists"
|
||||
|
||||
```bash
|
||||
# Check existing branches
|
||||
git branch -r | grep dev/
|
||||
|
||||
# Delete remote branch if needed (carefully!)
|
||||
git push origin --delete dev/03.06.00
|
||||
```
|
||||
|
||||
#### "Missing required secrets"
|
||||
|
||||
Go to repository Settings → Secrets and variables → Actions, and add:
|
||||
- `FTP_SERVER`
|
||||
- `FTP_USER`
|
||||
- `FTP_KEY` or `FTP_PASSWORD`
|
||||
- `FTP_PATH`
|
||||
|
||||
#### SFTP Connection Failed
|
||||
|
||||
1. Verify credentials in repo_health workflow:
|
||||
- Actions → Repo Health → profile: release
|
||||
|
||||
2. Check SSH key format (OpenSSH, not PuTTY PPK)
|
||||
|
||||
3. Verify server allows connections from GitHub IPs
|
||||
|
||||
### Quality Check Failures
|
||||
|
||||
#### PHPStan Errors
|
||||
|
||||
```bash
|
||||
# Run locally to see full output
|
||||
phpstan analyse --configuration=phpstan.neon
|
||||
|
||||
# Generate baseline to ignore existing issues
|
||||
phpstan analyse --configuration=phpstan.neon --generate-baseline
|
||||
```
|
||||
|
||||
#### PHPCS Violations
|
||||
|
||||
```bash
|
||||
# Check violations
|
||||
phpcs --standard=phpcs.xml src/
|
||||
|
||||
# Auto-fix where possible
|
||||
phpcbf --standard=phpcs.xml src/
|
||||
|
||||
# Show specific error codes
|
||||
phpcs --standard=phpcs.xml --report=source src/
|
||||
```
|
||||
|
||||
#### Joomla Testing Failed
|
||||
|
||||
1. Check PHP/Joomla version matrix compatibility
|
||||
2. Review MySQL connection errors
|
||||
3. Verify template manifest structure
|
||||
4. Check template file paths
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Version Management
|
||||
|
||||
1. **Always use version branches:** dev/X.Y.Z, rc/X.Y.Z, version/X.Y.Z
|
||||
2. **Follow hierarchy:** dev → rc → version → main
|
||||
3. **Update CHANGELOG:** Document all changes in Unreleased section
|
||||
4. **Semantic versioning:** Major.Minor.Patch (03.06.00)
|
||||
|
||||
### Code Quality
|
||||
|
||||
1. **Run validations locally** before pushing
|
||||
2. **Fix PHPStan warnings** at level 5
|
||||
3. **Follow PSR-12** coding standards
|
||||
4. **Add SPDX license headers** to new files
|
||||
5. **Keep functions small** and well-documented
|
||||
|
||||
### Workflow Usage
|
||||
|
||||
1. **Use CI for every commit** - automated validation
|
||||
2. **Run repo_health before releases** - comprehensive checks
|
||||
3. **Test on staging first** - never deploy directly to production
|
||||
4. **Monitor workflow runs** - fix failures promptly
|
||||
5. **Review workflow logs** - understand what changed
|
||||
|
||||
### Release Process
|
||||
|
||||
1. **Create dev branch** → Work on features
|
||||
2. **Promote to rc** → Release candidate testing
|
||||
3. **Promote to version** → Stable release
|
||||
4. **Merge to main** → Production (auto-merged via PR)
|
||||
5. **Create GitHub Release** → Public distribution
|
||||
|
||||
### Security
|
||||
|
||||
1. **Never commit secrets** - use GitHub Secrets
|
||||
2. **Use SSH keys** for SFTP (not passwords)
|
||||
3. **Scan for secrets** - runs automatically in CI
|
||||
4. **Keep dependencies updated** - security patches
|
||||
5. **Review security advisories** - GitHub Dependabot
|
||||
|
||||
### Documentation
|
||||
|
||||
1. **Update docs with code** - keep in sync
|
||||
2. **Document workflow changes** - update this guide
|
||||
3. **Add examples** - show, don't just tell
|
||||
4. **Link to relevant docs** - cross-reference
|
||||
5. **Keep README current** - first impression matters
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Main README](../README.md) - Project overview
|
||||
- [Joomla Development Guide](./JOOMLA_DEVELOPMENT.md) - Testing and quality
|
||||
- [CHANGELOG](../CHANGELOG.md) - Version history
|
||||
- [CONTRIBUTING](../CONTRIBUTING.md) - Contribution guidelines
|
||||
|
||||
## Getting Help
|
||||
|
||||
1. **Check workflow logs** - Most issues have clear error messages
|
||||
2. **Review this guide** - Common solutions documented
|
||||
3. **Run validation scripts** - Identify specific issues
|
||||
4. **Open an issue** - For bugs or questions
|
||||
5. **Contact maintainers** - For access or configuration issues
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
* Document: docs/WORKFLOW_GUIDE.md
|
||||
* Repository: [https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx](https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx)
|
||||
* Path: /docs/WORKFLOW_GUIDE.md
|
||||
* Owner: Moko Consulting
|
||||
* Version: 03.06.03
|
||||
* Status: Active
|
||||
* Effective Date: 2026-01-30
|
||||
* Classification: Public Open Source Documentation
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Summary | Author |
|
||||
| ---------- | ----------------------------------------------------- | --------------- |
|
||||
| 2026-01-30 | Updated metadata to MokoStandards format | GitHub Copilot |
|
||||
| 2025-01-04 | Initial workflow guide created | GitHub Copilot |
|
||||
12
package.json
Normal file
12
package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "mokoonyx-build",
|
||||
"private": true,
|
||||
"description": "Build tooling for MokoOnyx Joomla template",
|
||||
"scripts": {
|
||||
"minify": "node scripts/minify.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-css": "^5.3.3",
|
||||
"terser": "^5.39.0"
|
||||
}
|
||||
}
|
||||
77
phpcs.xml
Normal file
77
phpcs.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="Joomla Coding Standards">
|
||||
<description>Joomla coding standards for MokoOnyx</description>
|
||||
|
||||
<!-- Show progress and sniff names -->
|
||||
<arg value="ps"/>
|
||||
|
||||
<!-- Use colors in output -->
|
||||
<arg name="colors"/>
|
||||
|
||||
<!-- Check PHP files only -->
|
||||
<arg name="extensions" value="php"/>
|
||||
|
||||
<!-- Exclude patterns -->
|
||||
<exclude-pattern>*/node_modules/*</exclude-pattern>
|
||||
<exclude-pattern>*/vendor/*</exclude-pattern>
|
||||
<exclude-pattern>*/tests/_output/*</exclude-pattern>
|
||||
<exclude-pattern>*/cache/*</exclude-pattern>
|
||||
<exclude-pattern>*/tmp/*</exclude-pattern>
|
||||
<exclude-pattern>*/.git/*</exclude-pattern>
|
||||
|
||||
<!-- Include src directory -->
|
||||
<file>src</file>
|
||||
|
||||
<!-- Use Joomla coding standard as base -->
|
||||
<!-- When Joomla standard is installed, uncomment: -->
|
||||
<!-- <rule ref="Joomla"/> -->
|
||||
|
||||
<!-- PSR-12 as fallback base standard -->
|
||||
<rule ref="PSR12">
|
||||
<!-- Allow long lines in some cases -->
|
||||
<exclude name="Generic.Files.LineLength"/>
|
||||
</rule>
|
||||
|
||||
<!-- Additional rules for PHP compatibility -->
|
||||
<rule ref="PHPCompatibility"/>
|
||||
|
||||
<!-- Set minimum PHP version for compatibility checks -->
|
||||
<config name="testVersion" value="8.0-"/>
|
||||
|
||||
<!-- Check for deprecated PHP functions -->
|
||||
<rule ref="Generic.PHP.DeprecatedFunctions"/>
|
||||
|
||||
<!-- Enforce proper file naming -->
|
||||
<rule ref="Generic.Files.ByteOrderMark"/>
|
||||
<rule ref="Generic.Files.LineEndings">
|
||||
<properties>
|
||||
<property name="eolChar" value="\n"/>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<!-- Code structure -->
|
||||
<rule ref="Generic.Classes.DuplicateClassName"/>
|
||||
<rule ref="Generic.CodeAnalysis.EmptyStatement"/>
|
||||
<rule ref="Generic.CodeAnalysis.UnconditionalIfStatement"/>
|
||||
<rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/>
|
||||
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod"/>
|
||||
|
||||
<!-- Naming conventions -->
|
||||
<rule ref="Generic.NamingConventions.UpperCaseConstantName"/>
|
||||
|
||||
<!-- Security -->
|
||||
<rule ref="Generic.PHP.BacktickOperator"/>
|
||||
|
||||
<!-- Formatting -->
|
||||
<rule ref="Generic.Formatting.DisallowMultipleStatements"/>
|
||||
<rule ref="Generic.Formatting.SpaceAfterCast"/>
|
||||
|
||||
<!-- White space -->
|
||||
<rule ref="Generic.WhiteSpace.DisallowTabIndent"/>
|
||||
<rule ref="Generic.WhiteSpace.ScopeIndent">
|
||||
<properties>
|
||||
<property name="indent" value="4"/>
|
||||
<property name="tabIndent" value="true"/>
|
||||
</properties>
|
||||
</rule>
|
||||
</ruleset>
|
||||
32
phpstan.neon
Normal file
32
phpstan.neon
Normal file
@@ -0,0 +1,32 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# PHPStan configuration for Joomla extension repositories.
|
||||
# Extends the base MokoStandards config and adds Joomla framework class stubs
|
||||
# so PHPStan can resolve Factory, CMSApplication, User, Table, etc.
|
||||
# without requiring a full Joomla installation.
|
||||
|
||||
parameters:
|
||||
level: 5
|
||||
|
||||
paths:
|
||||
- src
|
||||
|
||||
excludePaths:
|
||||
- vendor
|
||||
- node_modules
|
||||
|
||||
# Joomla framework stubs — resolved via the enterprise package from vendor/
|
||||
stubFiles:
|
||||
- vendor/mokoconsulting-tech/enterprise/templates/stubs/joomla.php
|
||||
|
||||
# Suppress errors that are structural in Joomla's service-container architecture
|
||||
ignoreErrors:
|
||||
# Joomla's service-based dependency injection returns mixed from getApplication()
|
||||
- '#Cannot call method .+ on Joomla\\CMS\\Application\\CMSApplication\|null#'
|
||||
# Factory::getX() patterns are safe at runtime even when nullable in stubs
|
||||
- '#Call to static method [a-zA-Z]+\(\) on an interface#'
|
||||
|
||||
reportUnmatchedIgnoredErrors: false
|
||||
checkMissingIterableValueType: false
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
73
scripts/download-google-fonts.php
Normal file
73
scripts/download-google-fonts.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* # FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Template.Site
|
||||
* INGROUP: MokoOnyx
|
||||
* PATH: scripts/download-google-fonts.php
|
||||
* VERSION: 03.09.03
|
||||
* BRIEF: Download Google Fonts (woff2) for local self-hosting
|
||||
*/
|
||||
|
||||
$fontsDir = __DIR__ . '/../src/media/fonts';
|
||||
|
||||
if (!is_dir($fontsDir)) {
|
||||
fwrite(STDERR, "Error: Fonts directory not found: {$fontsDir}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$fonts = [
|
||||
'Roboto' => 'https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;700&display=swap',
|
||||
'Noto Sans' => 'https://fonts.googleapis.com/css2?family=Noto+Sans:wght@100;300;400;700&display=swap',
|
||||
'Fira Sans' => 'https://fonts.googleapis.com/css2?family=Fira+Sans:wght@100;300;400;700&display=swap',
|
||||
];
|
||||
|
||||
$userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||
|
||||
echo "Google Fonts Downloader for MokoOnyx\n";
|
||||
echo str_repeat('=', 48) . "\n";
|
||||
echo "Target: {$fontsDir}\n\n";
|
||||
|
||||
foreach ($fonts as $name => $url) {
|
||||
echo "Downloading {$name}...\n";
|
||||
|
||||
$ctx = stream_context_create(['http' => ['header' => "User-Agent: {$userAgent}\r\n"]]);
|
||||
$css = @file_get_contents($url, false, $ctx);
|
||||
|
||||
if ($css === false) {
|
||||
fwrite(STDERR, " FAIL: could not fetch CSS for {$name}\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
preg_match_all('#https://fonts\.gstatic\.com[^)]*\.woff2#', $css, $matches);
|
||||
|
||||
if (empty($matches[0])) {
|
||||
fwrite(STDERR, " FAIL: no woff2 URLs found for {$name}\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
foreach ($matches[0] as $fontUrl) {
|
||||
$filename = basename($fontUrl);
|
||||
$dest = $fontsDir . '/' . $filename;
|
||||
|
||||
$data = @file_get_contents($fontUrl, false, $ctx);
|
||||
if ($data === false) {
|
||||
fwrite(STDERR, " FAIL: {$filename}\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
file_put_contents($dest, $data);
|
||||
$size = round(strlen($data) / 1024, 1);
|
||||
echo " OK: {$filename} ({$size} KB)\n";
|
||||
$count++;
|
||||
}
|
||||
|
||||
echo " {$count} file(s) downloaded\n\n";
|
||||
}
|
||||
|
||||
echo "Done.\n";
|
||||
170
src/component.php
Normal file
170
src/component.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
|
||||
/** @var Joomla\CMS\Document\HtmlDocument $this */
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$input = $app->getInput();
|
||||
$document = $app->getDocument();
|
||||
$wa = $document->getWebAssetManager();
|
||||
|
||||
// Template params - Component uses minimal configuration
|
||||
$params_googletagmanager = $this->params->get('googletagmanager', false);
|
||||
$params_googletagmanagerid = $this->params->get('googletagmanagerid', null);
|
||||
$params_googleanalytics = $this->params->get('googleanalytics', false);
|
||||
$params_googleanalyticsid = $this->params->get('googleanalyticsid', null);
|
||||
$params_googlesitekey = $this->params->get('googlesitekey', null);
|
||||
|
||||
if (!empty($params_googlesitekey)) {
|
||||
$this->setMetaData('google-site-verification', htmlspecialchars($params_googlesitekey, ENT_QUOTES, 'UTF-8'));
|
||||
}
|
||||
|
||||
// Detecting Active Variables
|
||||
$option = $input->getCmd('option', '');
|
||||
$view = $input->getCmd('view', '');
|
||||
$layout = $input->getCmd('layout', '');
|
||||
$task = $input->getCmd('task', '');
|
||||
$itemid = $input->getCmd('Itemid', '');
|
||||
$sitenameR = $app->get('sitename'); // raw for title composition
|
||||
$sitename = htmlspecialchars($sitenameR, ENT_QUOTES, 'UTF-8');
|
||||
$menu = $app->getMenu()->getActive();
|
||||
$pageclass = $menu !== null ? $menu->getParams()->get('pageclass_sfx', '') : '';
|
||||
|
||||
// Template/Media path
|
||||
$templatePath = 'media/templates/site/mokoonyx';
|
||||
|
||||
// Core template CSS
|
||||
$wa->useStyle('template.base'); // css/template.css
|
||||
|
||||
// Component always uses light theme only (no theme switching)
|
||||
$wa->useStyle('template.light.standard'); // css/theme/light.standard.css
|
||||
|
||||
// Load Osaka font for site title
|
||||
$wa->useStyle('template.font.osaka');
|
||||
|
||||
// Brand: logo from params OR siteTitle
|
||||
// -------------------------------------
|
||||
$brandHtml = '';
|
||||
$logoFile = (string) $this->params->get('logoFile');
|
||||
|
||||
if ($logoFile !== '') {
|
||||
$brandHtml = HTMLHelper::_(
|
||||
'image',
|
||||
Uri::root(false) . htmlspecialchars($logoFile, ENT_QUOTES, 'UTF-8'),
|
||||
$sitename,
|
||||
['class' => 'logo d-inline-block', 'loading' => 'eager', 'decoding' => 'async'],
|
||||
false,
|
||||
0
|
||||
);
|
||||
} else {
|
||||
// If no logo file, show the title (defaults to "MokoOnyx" if not set)
|
||||
$siteTitle = $this->params->get('siteTitle', 'MokoOnyx');
|
||||
$brandHtml = '<span class="site-title" title="' . $sitename . '">'
|
||||
. htmlspecialchars($siteTitle, ENT_COMPAT, 'UTF-8')
|
||||
. '</span>';
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html class="component" lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>" data-bs-theme="light">
|
||||
<head>
|
||||
<jdoc:include type="metas" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<jdoc:include type="styles" />
|
||||
<jdoc:include type="scripts" />
|
||||
</head>
|
||||
<body class="<?php echo $this->direction === 'rtl' ? 'rtl' : ''; ?>">
|
||||
<?php if (!empty($params_googletagmanager) && !empty($params_googletagmanagerid)) :
|
||||
$gtmID = htmlspecialchars($params_googletagmanagerid, ENT_QUOTES, 'UTF-8'); ?>
|
||||
<!-- Google Tag Manager -->
|
||||
<script>
|
||||
(function(w,d,s,l,i){
|
||||
w[l]=w[l]||[];
|
||||
w[l].push({'gtm.start': new Date().getTime(), event:'gtm.js'});
|
||||
var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),
|
||||
dl=l!='dataLayer'?'&l='+l:'';
|
||||
j.async=true;
|
||||
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
|
||||
f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','<?php echo $gtmID; ?>');
|
||||
</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript>
|
||||
<iframe src="https://www.googletagmanager.com/ns.html?id=<?php echo $gtmID; ?>"
|
||||
height="0" width="0" style="display:none;visibility:hidden"></iframe>
|
||||
</noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($params_googleanalytics) && !empty($params_googleanalyticsid)) :
|
||||
$gaId = htmlspecialchars($params_googleanalyticsid, ENT_QUOTES, 'UTF-8'); ?>
|
||||
<!-- Google Analytics (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo $gaId; ?>"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('consent', 'default', {
|
||||
'ad_storage': 'denied',
|
||||
'analytics_storage': 'granted',
|
||||
'ad_user_data': 'denied',
|
||||
'ad_personalization': 'denied'
|
||||
});
|
||||
(function(id){
|
||||
if (/^G-/.test(id)) {
|
||||
gtag('config', id, { 'anonymize_ip': true });
|
||||
} else if (/^UA-/.test(id)) {
|
||||
gtag('config', id, { 'anonymize_ip': true });
|
||||
console.warn('Using a UA- ID. Universal Analytics is sunset; consider migrating to GA4.');
|
||||
} else {
|
||||
console.warn('Unrecognized Google Analytics ID format:', id);
|
||||
}
|
||||
})('<?php echo $gaId; ?>');
|
||||
</script>
|
||||
<!-- End Google Analytics -->
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->params->get('brand', 1)) : ?>
|
||||
<div class="navbar-brand">
|
||||
<a class="brand-logo" href="<?php echo $this->baseurl; ?>/">
|
||||
<?php echo $brandHtml; ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<jdoc:include type="message" />
|
||||
<jdoc:include type="component" />
|
||||
|
||||
<footer class="container-footer footer full-width">
|
||||
<?php if ($this->countModules('footer-menu', true)) : ?>
|
||||
<div class="grid-child footer-menu">
|
||||
<jdoc:include type="modules" name="footer-menu" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($this->countModules('footer', true)) : ?>
|
||||
<div class="grid-child">
|
||||
<jdoc:include type="modules" name="footer" style="none" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</footer>
|
||||
|
||||
<jdoc:include type="modules" name="debug" style="none" />
|
||||
</body>
|
||||
</html>
|
||||
20
src/custom.php
Normal file
20
src/custom.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
function console_log($output, $with_script_tags = true) {
|
||||
$js_code = 'console.log(' . json_encode($output, JSON_HEX_TAG) .
|
||||
');';
|
||||
if ($with_script_tags) {
|
||||
$js_code = '<script>' . $js_code . '</script>';
|
||||
}
|
||||
echo $js_code;
|
||||
}
|
||||
?>
|
||||
<!--
|
||||
Custom code included here
|
||||
-->
|
||||
452
src/error.php
Normal file
452
src/error.php
Normal file
@@ -0,0 +1,452 @@
|
||||
<?php
|
||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
/** @var Joomla\CMS\Document\ErrorDocument|Joomla\CMS\Document\HtmlDocument $this */
|
||||
$app = Factory::getApplication();
|
||||
$params = $this->params;
|
||||
$wa = $this->getWebAssetManager();
|
||||
|
||||
// Template params
|
||||
$params_LightColorName = (string) $params->get('colorLightName', 'standard'); // standard|custom
|
||||
|
||||
$params_DarkColorName = (string) $params->get('colorDarkName', 'standard'); // standard|custom
|
||||
|
||||
$params_googletagmanager = $params->get('googletagmanager', false);
|
||||
$params_googletagmanagerid = $params->get('googletagmanagerid', '');
|
||||
$params_googleanalytics = $params->get('googleanalytics', false);
|
||||
$params_googleanalyticsid = $params->get('googleanalyticsid', '');
|
||||
$params_custom_head_start = $params->get('custom_head_start', '');
|
||||
$params_custom_head_end = $params->get('custom_head_end', '');
|
||||
$params_developmentmode = $params->get('developmentmode', false);
|
||||
|
||||
// ------------------ Params ------------------
|
||||
$fluidContainer = (bool) $params->get('fluidContainer', 0);
|
||||
$wrapper = $fluidContainer ? 'wrapper-fluid' : 'wrapper-static';
|
||||
$stickyHeader = (bool) $params->get('stickyHeader', 0);
|
||||
|
||||
// Drawer icon params (escaped)
|
||||
$params_leftIcon = htmlspecialchars($params->get('drawerLeftIcon', 'fa-solid fa-chevron-left'), ENT_QUOTES, 'UTF-8');
|
||||
$params_rightIcon = htmlspecialchars($params->get('drawerRightIcon', 'fa-solid fa-chevron-right'), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// Template/Media path
|
||||
$templatePath = 'media/templates/site/mokoonyx';
|
||||
|
||||
// ===========================
|
||||
// Web Asset Manager (WAM) — matches your joomla.asset.json
|
||||
// ===========================
|
||||
|
||||
// Core template CSS
|
||||
$wa->useStyle('template.base'); // css/template.css
|
||||
|
||||
// Load theme palette stylesheets based on configuration
|
||||
$wa->useStyle('template.light.standard'); // css/theme/light.standard.css
|
||||
$wa->useStyle('template.dark.standard'); // css/theme/dark.standard.css
|
||||
|
||||
// Load custom palettes only if selected in template configuration AND files exist
|
||||
if ($params_LightColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templates/site/mokoonyx/css/theme/light.custom.css'))
|
||||
{
|
||||
$wa->useStyle('template.light.custom');
|
||||
}
|
||||
if ($params_DarkColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templates/site/mokoonyx/css/theme/dark.custom.css'))
|
||||
{
|
||||
$wa->useStyle('template.dark.custom');
|
||||
}
|
||||
|
||||
// Scripts
|
||||
$wa->useScript('template.js');
|
||||
|
||||
// Load Osaka font for site title
|
||||
$wa->useStyle('template.font.osaka');
|
||||
|
||||
// Smart Bootstrap component loading - only load what's needed
|
||||
if ($this->countModules('drawer-left', true) || $this->countModules('drawer-right', true)) {
|
||||
// Load Bootstrap Offcanvas component for drawers
|
||||
HTMLHelper::_('bootstrap.offcanvas');
|
||||
}
|
||||
|
||||
// Meta
|
||||
$this->setMetaData('viewport', 'width=device-width, initial-scale=1');
|
||||
|
||||
if ($this->params->get('faKitCode')) {
|
||||
$faKit = "https://kit.fontawesome.com/" . $this->params->get('faKitCode') . ".js";
|
||||
HTMLHelper::_('script', $faKit, ['crossorigin' => 'anonymous']);
|
||||
} else {
|
||||
try {
|
||||
if ($params_developmentmode){
|
||||
$wa->useStyle('vendor.fa7free.all');
|
||||
$wa->useStyle('vendor.fa7free.brands');
|
||||
$wa->useStyle('vendor.fa7free.fontawesome');
|
||||
$wa->useStyle('vendor.fa7free.regular');
|
||||
$wa->useStyle('vendor.fa7free.solid');
|
||||
} else {
|
||||
$wa->useStyle('vendor.fa7free.all.min');
|
||||
$wa->useStyle('vendor.fa7free.brands.min');
|
||||
$wa->useStyle('vendor.fa7free.fontawesome.min');
|
||||
$wa->useStyle('vendor.fa7free.regular.min');
|
||||
$wa->useStyle('vendor.fa7free.solid.min');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
if ($params_developmentmode){
|
||||
$wa->registerAndUseStyle('vendor.fa7free.all.dynamic', $templatePath . '/vendor/fa7free/css/all.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.brands.dynamic', $templatePath . '/vendor/fa7free/css/brands.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.fontawesome.dynamic', $templatePath . '/vendor/fa7free/css/fontawesome.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.regular.dynamic', $templatePath . '/vendor/fa7free/css/regular.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.solid.dynamic', $templatePath . '/vendor/fa7free/css/solid.css');
|
||||
} else {
|
||||
$wa->registerAndUseStyle('vendor.fa7free.all.min.dynamic', $templatePath . '/vendor/fa7free/css/all.min.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.brands.min.dynamic', $templatePath . '/vendor/fa7free/css/brands.min.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.fontawesome.min.dynamic', $templatePath . '/vendor/fa7free/css/fontawesome.min.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.regular.min.dynamic', $templatePath . '/vendor/fa7free/css/regular.min.css');
|
||||
$wa->registerAndUseStyle('vendor.fa7free.solid.min.dynamic', $templatePath . '/vendor/fa7free/css/solid.min.css');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------ Context (logo, bootstrap needs) ------------------
|
||||
$sitename = htmlspecialchars($app->get('sitename'), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// -------------------------------------
|
||||
// Brand: logo from params OR siteTitle
|
||||
// -------------------------------------
|
||||
$brandHtml = '';
|
||||
$logoFile = (string) $this->params->get('logoFile');
|
||||
|
||||
if ($logoFile !== '') {
|
||||
$brandHtml = HTMLHelper::_(
|
||||
'image',
|
||||
Uri::root(false) . htmlspecialchars($logoFile, ENT_QUOTES, 'UTF-8'),
|
||||
$sitename,
|
||||
['class' => 'logo d-inline-block', 'loading' => 'eager', 'decoding' => 'async'],
|
||||
false,
|
||||
0
|
||||
);
|
||||
} else {
|
||||
// If no logo file, show the title (defaults to "MokoOnyx" if not set)
|
||||
$siteTitle = $this->params->get('siteTitle', 'MokoOnyx');
|
||||
$brandHtml = '<span class="site-title" title="' . $sitename . '">'
|
||||
. htmlspecialchars($siteTitle, ENT_COMPAT, 'UTF-8')
|
||||
. '</span>';
|
||||
}
|
||||
|
||||
// ------------------ Error details ------------------
|
||||
$errorObj = isset($this->error) && is_object($this->error) ? $this->error : null;
|
||||
$errorCode = $errorObj ? (int) $errorObj->getCode() : 500;
|
||||
$errorMsg = $errorObj ? $errorObj->getMessage() : Text::_('JERROR_AN_ERROR_HAS_OCCURRED');
|
||||
$debugOn = defined('JDEBUG') && JDEBUG;
|
||||
|
||||
// Load user assets last (after all other styles and scripts)
|
||||
$wa->useStyle('template.user'); // css/user.css
|
||||
$wa->useScript('user.js'); // js/user.js
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
|
||||
<head>
|
||||
<?php if ($params_custom_head_start !== '') : ?><?php echo $params_custom_head_start; ?><?php endif; ?>
|
||||
<jdoc:include type="head" />
|
||||
|
||||
<script>
|
||||
// Early theme application to avoid FOUC
|
||||
(function () {
|
||||
try {
|
||||
var stored = localStorage.getItem('theme');
|
||||
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
var theme = stored ? stored : (prefersDark ? 'dark' : 'light');
|
||||
document.documentElement.setAttribute('data-bs-theme', theme);
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Facebook in-app browser warning banner
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
var ua = navigator.userAgent || navigator.vendor || window.opera;
|
||||
var isFacebookBrowser = ua.indexOf('FBAN') > -1 || ua.indexOf('FBAV') > -1;
|
||||
if (isFacebookBrowser) {
|
||||
var warning = document.createElement('div');
|
||||
warning.textContent = '⚠️ KNOWN ISSUE: Images do not load in Facebook Web browser. Please open in external browser for full experience.';
|
||||
warning.style.position = 'fixed';
|
||||
warning.style.top = '0';
|
||||
warning.style.left = '0';
|
||||
warning.style.right = '0';
|
||||
warning.style.zIndex = '10000';
|
||||
warning.style.backgroundColor = '#007bff';
|
||||
warning.style.color = '#fff';
|
||||
warning.style.padding = '15px';
|
||||
warning.style.textAlign = 'center';
|
||||
warning.style.fontWeight = 'bold';
|
||||
warning.style.fontSize = '16px';
|
||||
warning.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
|
||||
document.body.appendChild(warning);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php if ($params_custom_head_end !== '') : ?><?php echo $params_custom_head_end; ?><?php endif; ?>
|
||||
</head>
|
||||
<body data-bs-spy="scroll" data-bs-target="#toc" class="site error-page<?php
|
||||
echo ($this->direction == 'rtl' ? ' rtl' : '');
|
||||
?>">
|
||||
<?php if (!empty($params_googletagmanager) && !empty($params_googletagmanagerid)) : ?>
|
||||
<!-- Google Tag Manager -->
|
||||
<script>
|
||||
(function(w,d,s,l,i){
|
||||
w[l]=w[l]||[];
|
||||
w[l].push({'gtm.start': new Date().getTime(), event:'gtm.js'});
|
||||
var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),
|
||||
dl=l!='dataLayer'?'&l='+l:'';
|
||||
j.async=true;
|
||||
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
|
||||
f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer',<?php echo json_encode($params_googletagmanagerid, JSON_HEX_TAG | JSON_HEX_AMP); ?>);
|
||||
</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript>
|
||||
<iframe src="https://www.googletagmanager.com/ns.html?id=<?php echo htmlspecialchars($params_googletagmanagerid, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
height="0" width="0" style="display:none;visibility:hidden"></iframe>
|
||||
</noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($params_googleanalytics) && !empty($params_googleanalyticsid)) : ?>
|
||||
<!-- Google Analytics (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo htmlspecialchars($params_googleanalyticsid, ENT_QUOTES, 'UTF-8'); ?>"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('consent', 'default', {
|
||||
'ad_storage': 'denied',
|
||||
'analytics_storage': 'granted',
|
||||
'ad_user_data': 'denied',
|
||||
'ad_personalization': 'denied'
|
||||
});
|
||||
(function(id){
|
||||
if (/^G-/.test(id)) {
|
||||
gtag('config', id, { 'anonymize_ip': true });
|
||||
} else if (/^UA-/.test(id)) {
|
||||
gtag('config', id, { 'anonymize_ip': true });
|
||||
console.warn('Using a UA- ID. Universal Analytics is sunset; consider migrating to GA4.');
|
||||
} else {
|
||||
console.warn('Unrecognized Google Analytics ID format:', id);
|
||||
}
|
||||
})(<?php echo json_encode($params_googleanalyticsid, JSON_HEX_TAG | JSON_HEX_AMP); ?>);
|
||||
</script>
|
||||
<!-- End Google Analytics -->
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ========== HEADER FROM INDEX ========== -->
|
||||
<header class="header container-header full-width<?php echo $stickyHeader ? ' position-sticky sticky-top' : ''; ?>" role="banner">
|
||||
|
||||
<?php if ($this->countModules('topbar')) : ?>
|
||||
<div class="container-topbar">
|
||||
<jdoc:include type="modules" name="topbar" style="none" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="header-top">
|
||||
<?php if ($this->countModules('below-topbar')) : ?>
|
||||
<div class="grid-child container-below-topbar">
|
||||
<jdoc:include type="modules" name="below-topbar" style="none" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->params->get('brand', 1)) : ?>
|
||||
<div class="grid-child">
|
||||
<div class="navbar-brand">
|
||||
<a class="brand-logo" href="<?php echo $this->baseurl; ?>/">
|
||||
<?php echo $brandHtml; ?>
|
||||
</a>
|
||||
<?php if ($this->params->get('siteDescription')) : ?>
|
||||
<div class="site-description">
|
||||
<?php echo htmlspecialchars($this->params->get('siteDescription'), ENT_QUOTES, 'UTF-8'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->countModules('below-logo')) : ?>
|
||||
<div class="grid container-below-logo">
|
||||
<jdoc:include type="modules" name="below-logo" style="none" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Drawer Toggle Buttons -->
|
||||
<?php if ($this->countModules('drawer-left')) : ?>
|
||||
<button class="drawer-toggle-left btn btn-outline-secondary me-2"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#drawer-left"
|
||||
aria-controls="drawer-left">
|
||||
<span class="<?php echo $params_leftIcon; ?>"></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->countModules('drawer-right')) : ?>
|
||||
<button class="drawer-toggle-right btn btn-outline-secondary"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#drawer-right"
|
||||
aria-controls="drawer-right">
|
||||
<span class="<?php echo $params_rightIcon; ?>"></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->countModules('menu', true) || $this->countModules('search', true)) : ?>
|
||||
<div class="grid-child container-nav">
|
||||
<?php if ($this->countModules('menu', true)) : ?>
|
||||
<nav role="navigation" aria-label="Primary">
|
||||
<jdoc:include type="modules" name="menu" style="none" />
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
<?php if ($this->countModules('search', true)) : ?>
|
||||
<div class="container-search">
|
||||
<jdoc:include type="modules" name="search" style="none" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
<!-- ========== END HEADER ========== -->
|
||||
|
||||
<main class="container my-4">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">
|
||||
<span class="text-muted"><?php echo Text::_('JERROR_LAYOUT_ERROR_HAS_OCCURRED'); ?>:</span>
|
||||
<strong><?php echo (int) $errorCode; ?></strong>
|
||||
</h1>
|
||||
<p class="lead mb-1">
|
||||
<?php echo htmlspecialchars($errorMsg, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</p>
|
||||
<p class="text-muted mb-0">
|
||||
<?php echo Text::_('JERROR_LAYOUT_PLEASE_TRY_ONE_OF_THE_FOLLOWING_PAGES'); ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<a class="btn btn-primary" href="<?php echo htmlspecialchars(Uri::base(), ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<i class="fa-solid fa-home me-1" aria-hidden="true"></i>
|
||||
<?php echo Text::_('JERROR_LAYOUT_HOME_PAGE'); ?>
|
||||
</a>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="history.back();">
|
||||
<i class="fa-solid fa-arrow-left me-1" aria-hidden="true"></i>
|
||||
<?php echo Text::_('JPREV'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php if ($debugOn && $errorObj) : ?>
|
||||
<section class="mt-4" role="region" aria-label="Debug Details">
|
||||
<div class="alert alert-warning"><strong>Debug mode is ON</strong> — detailed error information is shown below.</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header fw-bold">Exception</div>
|
||||
<div class="card-body small">
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-3">Class</dt>
|
||||
<dd class="col-sm-9"><?php echo htmlspecialchars(get_class($errorObj), ENT_QUOTES, 'UTF-8'); ?></dd>
|
||||
|
||||
<dt class="col-sm-3">Code</dt>
|
||||
<dd class="col-sm-9"><?php echo (int) $errorObj->getCode(); ?></dd>
|
||||
|
||||
<dt class="col-sm-3">Message</dt>
|
||||
<dd class="col-sm-9 text-break"><?php echo htmlspecialchars($errorObj->getMessage(), ENT_QUOTES, 'UTF-8'); ?></dd>
|
||||
|
||||
<dt class="col-sm-3">File</dt>
|
||||
<dd class="col-sm-9 text-break"><?php echo htmlspecialchars($errorObj->getFile(), ENT_QUOTES, 'UTF-8'); ?> : <?php echo (int) $errorObj->getLine(); ?></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $trace = method_exists($errorObj, 'getTrace') ? $errorObj->getTrace() : []; ?>
|
||||
<div class="card mb-3">
|
||||
<div class="card-header fw-bold">Stack Trace (<?php echo count($trace); ?> frames)</div>
|
||||
<div class="card-body small">
|
||||
<?php if ($trace) : ?>
|
||||
<ol class="mb-0 ps-3">
|
||||
<?php foreach ($trace as $i => $frame) :
|
||||
$file = $frame['file'] ?? '[internal]';
|
||||
$line = isset($frame['line']) ? (int) $frame['line'] : 0;
|
||||
$func = $frame['function'] ?? '';
|
||||
$class= $frame['class'] ?? '';
|
||||
$type = $frame['type'] ?? '';
|
||||
?>
|
||||
<li class="mb-2">
|
||||
<div class="text-break"><code>#<?php echo $i; ?></code> <?php echo htmlspecialchars($class . $type . $func, ENT_QUOTES, 'UTF-8'); ?>()</div>
|
||||
<div class="text-muted"><?php echo htmlspecialchars($file, ENT_QUOTES, 'UTF-8'); ?><?php echo $line ? ':' . $line : ''; ?></div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
<?php else : ?>
|
||||
<em>No stack trace available.</em>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
<footer class="container-footer footer full-width">
|
||||
<?php if ($this->countModules('footer-menu', true)) : ?>
|
||||
<div class="grid-child footer-menu">
|
||||
<jdoc:include type="modules" name="footer-menu" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($this->countModules('footer', true)) : ?>
|
||||
<div class="grid-child">
|
||||
<jdoc:include type="modules" name="footer" style="none" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</footer>
|
||||
|
||||
<?php if ($this->params->get('backTop') == 1) : ?>
|
||||
<a href="#top" id="back-top" class="back-to-top-link" aria-label="<?php echo Text::_('TPL_MOKOONYX_BACKTOTOP'); ?>">
|
||||
<span class="fa-solid fa-arrow-up" aria-hidden="true"></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->countModules('drawer-left', true)) : ?>
|
||||
<!-- Left Offcanvas Drawer -->
|
||||
<aside class="offcanvas offcanvas-start" tabindex="-1" id="drawer-left">
|
||||
<div class="offcanvas-header">
|
||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="<?php echo Text::_('JLIB_HTML_BEHAVIOR_CLOSE'); ?>"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<jdoc:include type="modules" name="drawer-left" style="none" />
|
||||
</div>
|
||||
</aside>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->countModules('drawer-right', true)) : ?>
|
||||
<!-- Right Offcanvas Drawer -->
|
||||
<aside class="offcanvas offcanvas-end" tabindex="-1" id="drawer-right">
|
||||
<div class="offcanvas-header">
|
||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="<?php echo Text::_('JLIB_HTML_BEHAVIOR_CLOSE'); ?>"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<jdoc:include type="modules" name="drawer-right" style="none" />
|
||||
</div>
|
||||
</aside>
|
||||
<?php endif; ?>
|
||||
|
||||
<jdoc:include type="modules" name="debug" style="none" />
|
||||
|
||||
</body>
|
||||
</html>
|
||||
518
src/helper/favicon.php
Normal file
518
src/helper/favicon.php
Normal file
@@ -0,0 +1,518 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Favicon generator — creates ICO, Apple Touch Icon, and Android icons
|
||||
* from a single source image uploaded via the template config.
|
||||
*
|
||||
* Supports three backends in priority order:
|
||||
* 1. GD (fastest, most common)
|
||||
* 2. Imagick (common on shared hosting)
|
||||
* 3. Pure PHP (zero-dependency fallback using raw PNG manipulation)
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
class MokoFaviconHelper
|
||||
{
|
||||
private const SIZES = [
|
||||
'apple-touch-icon.png' => [180, 180],
|
||||
'favicon-32x32.png' => [32, 32],
|
||||
'favicon-16x16.png' => [16, 16],
|
||||
'android-chrome-192x192.png' => [192, 192],
|
||||
'android-chrome-512x512.png' => [512, 512],
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate all favicon files from a source image.
|
||||
*/
|
||||
public static function generate(string $sourcePath, string $outputDir): bool
|
||||
{
|
||||
if (!is_file($sourcePath)) {
|
||||
self::log('Favicon: source file not found: ' . $sourcePath, 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_dir($outputDir)) {
|
||||
mkdir($outputDir, 0755, true);
|
||||
}
|
||||
|
||||
$sourceTime = filemtime($sourcePath);
|
||||
$stampFile = $outputDir . '/.favicon_generated';
|
||||
|
||||
if (is_file($stampFile) && filemtime($stampFile) >= $sourceTime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Strip #joomlaImage fragment if present
|
||||
$sourcePath = strtok($sourcePath, '#');
|
||||
|
||||
// Select backend
|
||||
if (extension_loaded('gd')) {
|
||||
$result = self::generateWithGd($sourcePath, $outputDir);
|
||||
} elseif (extension_loaded('imagick')) {
|
||||
$result = self::generateWithImagick($sourcePath, $outputDir);
|
||||
} else {
|
||||
$result = self::generatePurePHP($sourcePath, $outputDir);
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
self::generateManifest($outputDir);
|
||||
file_put_contents($stampFile, date('c'));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ── GD Backend ──────────────────────────────────────────────────
|
||||
|
||||
private static function generateWithGd(string $sourcePath, string $outputDir): bool
|
||||
{
|
||||
$imageInfo = @getimagesize($sourcePath);
|
||||
if ($imageInfo === false) {
|
||||
self::log('Favicon: cannot read image: ' . $sourcePath, 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
$source = match ($imageInfo[2]) {
|
||||
IMAGETYPE_PNG => @imagecreatefrompng($sourcePath),
|
||||
IMAGETYPE_JPEG => @imagecreatefromjpeg($sourcePath),
|
||||
IMAGETYPE_GIF => @imagecreatefromgif($sourcePath),
|
||||
IMAGETYPE_WEBP => function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($sourcePath) : false,
|
||||
default => false,
|
||||
};
|
||||
|
||||
if (!$source) {
|
||||
self::log('Favicon: unsupported image type', 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
imagealphablending($source, false);
|
||||
imagesavealpha($source, true);
|
||||
$srcW = imagesx($source);
|
||||
$srcH = imagesy($source);
|
||||
|
||||
foreach (self::SIZES as $filename => [$w, $h]) {
|
||||
$resized = imagecreatetruecolor($w, $h);
|
||||
imagealphablending($resized, false);
|
||||
imagesavealpha($resized, true);
|
||||
imagefill($resized, 0, 0, imagecolorallocatealpha($resized, 0, 0, 0, 127));
|
||||
imagecopyresampled($resized, $source, 0, 0, 0, 0, $w, $h, $srcW, $srcH);
|
||||
imagepng($resized, $outputDir . '/' . $filename, 9);
|
||||
imagedestroy($resized);
|
||||
}
|
||||
|
||||
// ICO from GD
|
||||
$icoEntries = [];
|
||||
foreach ([16, 32] as $size) {
|
||||
$resized = imagecreatetruecolor($size, $size);
|
||||
imagealphablending($resized, false);
|
||||
imagesavealpha($resized, true);
|
||||
imagefill($resized, 0, 0, imagecolorallocatealpha($resized, 0, 0, 0, 127));
|
||||
imagecopyresampled($resized, $source, 0, 0, 0, 0, $size, $size, $srcW, $srcH);
|
||||
ob_start();
|
||||
imagepng($resized, null, 9);
|
||||
$icoEntries[] = ['size' => $size, 'data' => ob_get_clean()];
|
||||
imagedestroy($resized);
|
||||
}
|
||||
self::writeIco($icoEntries, $outputDir . '/favicon.ico');
|
||||
|
||||
imagedestroy($source);
|
||||
self::log('Favicon: generated with GD');
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Imagick Backend ─────────────────────────────────────────────
|
||||
|
||||
private static function generateWithImagick(string $sourcePath, string $outputDir): bool
|
||||
{
|
||||
try {
|
||||
foreach (self::SIZES as $filename => [$w, $h]) {
|
||||
$img = new \Imagick($sourcePath);
|
||||
$img->setImageFormat('png');
|
||||
$img->setImageCompressionQuality(95);
|
||||
$img->thumbnailImage($w, $h, true);
|
||||
// Center on transparent canvas if not square
|
||||
$canvas = new \Imagick();
|
||||
$canvas->newImage($w, $h, new \ImagickPixel('transparent'), 'png');
|
||||
$offsetX = (int)(($w - $img->getImageWidth()) / 2);
|
||||
$offsetY = (int)(($h - $img->getImageHeight()) / 2);
|
||||
$canvas->compositeImage($img, \Imagick::COMPOSITE_OVER, $offsetX, $offsetY);
|
||||
$canvas->writeImage($outputDir . '/' . $filename);
|
||||
$img->destroy();
|
||||
$canvas->destroy();
|
||||
}
|
||||
|
||||
// ICO from Imagick
|
||||
$icoEntries = [];
|
||||
foreach ([16, 32] as $size) {
|
||||
$img = new \Imagick($sourcePath);
|
||||
$img->setImageFormat('png');
|
||||
$img->thumbnailImage($size, $size, true);
|
||||
$icoEntries[] = ['size' => $size, 'data' => (string) $img];
|
||||
$img->destroy();
|
||||
}
|
||||
self::writeIco($icoEntries, $outputDir . '/favicon.ico');
|
||||
|
||||
self::log('Favicon: generated with Imagick');
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::log('Favicon: Imagick failed: ' . $e->getMessage(), 'warning');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pure PHP Backend (zero dependencies) ────────────────────────
|
||||
|
||||
private static function generatePurePHP(string $sourcePath, string $outputDir): bool
|
||||
{
|
||||
$pngData = @file_get_contents($sourcePath);
|
||||
if ($pngData === false) {
|
||||
self::log('Favicon: cannot read source file', 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect format — we can only resize PNG in pure PHP
|
||||
// For JPEG/other formats, just copy the source as-is for each size
|
||||
$isPng = (substr($pngData, 0, 8) === "\x89PNG\r\n\x1a\n");
|
||||
|
||||
if (!$isPng) {
|
||||
// Non-PNG: copy source file for all sizes (no resize capability without extensions)
|
||||
foreach (self::SIZES as $filename => [$w, $h]) {
|
||||
copy($sourcePath, $outputDir . '/' . $filename);
|
||||
}
|
||||
// ICO: embed the raw source for 16 and 32 entries
|
||||
self::writeIco([
|
||||
['size' => 16, 'data' => $pngData],
|
||||
['size' => 32, 'data' => $pngData],
|
||||
], $outputDir . '/favicon.ico');
|
||||
self::log('Favicon: non-PNG source copied without resize (no GD/Imagick)');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse PNG dimensions from IHDR
|
||||
$ihdr = self::parsePngIhdr($pngData);
|
||||
if (!$ihdr) {
|
||||
self::log('Favicon: cannot parse PNG header', 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
$srcW = $ihdr['width'];
|
||||
$srcH = $ihdr['height'];
|
||||
|
||||
// Decode PNG to raw RGBA pixel array
|
||||
$pixels = self::decodePngToRgba($pngData, $srcW, $srcH, $ihdr);
|
||||
if ($pixels === null) {
|
||||
// Fallback: copy source for all sizes
|
||||
foreach (self::SIZES as $filename => [$w, $h]) {
|
||||
copy($sourcePath, $outputDir . '/' . $filename);
|
||||
}
|
||||
self::writeIco([
|
||||
['size' => 16, 'data' => $pngData],
|
||||
['size' => 32, 'data' => $pngData],
|
||||
], $outputDir . '/favicon.ico');
|
||||
self::log('Favicon: PNG decode failed, copied source without resize');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate resized PNGs
|
||||
foreach (self::SIZES as $filename => [$w, $h]) {
|
||||
$resized = self::resizePixels($pixels, $srcW, $srcH, $w, $h);
|
||||
$png = self::encodePng($resized, $w, $h);
|
||||
file_put_contents($outputDir . '/' . $filename, $png);
|
||||
}
|
||||
|
||||
// ICO
|
||||
$icoEntries = [];
|
||||
foreach ([16, 32] as $size) {
|
||||
$resized = self::resizePixels($pixels, $srcW, $srcH, $size, $size);
|
||||
$icoEntries[] = ['size' => $size, 'data' => self::encodePng($resized, $size, $size)];
|
||||
}
|
||||
self::writeIco($icoEntries, $outputDir . '/favicon.ico');
|
||||
|
||||
self::log('Favicon: generated with pure PHP');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse PNG IHDR chunk.
|
||||
*/
|
||||
private static function parsePngIhdr(string $data): ?array
|
||||
{
|
||||
if (strlen($data) < 33) return null;
|
||||
// Skip 8-byte signature, 4-byte chunk length, 4-byte "IHDR"
|
||||
$width = unpack('N', substr($data, 16, 4))[1];
|
||||
$height = unpack('N', substr($data, 20, 4))[1];
|
||||
$bitDepth = ord($data[24]);
|
||||
$colorType = ord($data[25]);
|
||||
return ['width' => $width, 'height' => $height, 'bitDepth' => $bitDepth, 'colorType' => $colorType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode PNG to flat RGBA array using zlib decompression.
|
||||
*
|
||||
* @return array|null Flat array of [r,g,b,a, r,g,b,a, ...] or null on failure.
|
||||
*/
|
||||
private static function decodePngToRgba(string $data, int $w, int $h, array $ihdr): ?array
|
||||
{
|
||||
// Only support 8-bit RGBA (color type 6) and RGB (color type 2) for simplicity
|
||||
$colorType = $ihdr['colorType'];
|
||||
$bitDepth = $ihdr['bitDepth'];
|
||||
|
||||
if ($bitDepth !== 8 || ($colorType !== 6 && $colorType !== 2 && $colorType !== 3)) {
|
||||
return null; // Unsupported format
|
||||
}
|
||||
|
||||
// Collect all IDAT chunks
|
||||
$idatData = '';
|
||||
$pos = 8; // Skip PNG signature
|
||||
$palette = null;
|
||||
$trns = null;
|
||||
|
||||
while ($pos < strlen($data) - 4) {
|
||||
$chunkLen = unpack('N', substr($data, $pos, 4))[1];
|
||||
$chunkType = substr($data, $pos + 4, 4);
|
||||
|
||||
if ($chunkType === 'IDAT') {
|
||||
$idatData .= substr($data, $pos + 8, $chunkLen);
|
||||
} elseif ($chunkType === 'PLTE') {
|
||||
$palette = substr($data, $pos + 8, $chunkLen);
|
||||
} elseif ($chunkType === 'tRNS') {
|
||||
$trns = substr($data, $pos + 8, $chunkLen);
|
||||
} elseif ($chunkType === 'IEND') {
|
||||
break;
|
||||
}
|
||||
|
||||
$pos += 12 + $chunkLen; // 4 len + 4 type + data + 4 crc
|
||||
}
|
||||
|
||||
$raw = @gzuncompress($idatData);
|
||||
if ($raw === false) {
|
||||
$raw = @gzinflate($idatData);
|
||||
}
|
||||
if ($raw === false) {
|
||||
// Try with zlib header
|
||||
$raw = @gzinflate(substr($idatData, 2));
|
||||
}
|
||||
if ($raw === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$bpp = $colorType === 6 ? 4 : ($colorType === 2 ? 3 : 1); // bytes per pixel
|
||||
$stride = 1 + $w * $bpp; // +1 for filter byte per row
|
||||
$pixels = [];
|
||||
|
||||
$prevRow = array_fill(0, $w * $bpp, 0);
|
||||
|
||||
for ($y = 0; $y < $h; $y++) {
|
||||
$rowStart = $y * $stride;
|
||||
if ($rowStart >= strlen($raw)) break;
|
||||
|
||||
$filter = ord($raw[$rowStart]);
|
||||
$row = [];
|
||||
|
||||
for ($x = 0; $x < $w * $bpp; $x++) {
|
||||
$rawByte = ord($raw[$rowStart + 1 + $x]);
|
||||
$a = ($x >= $bpp) ? $row[$x - $bpp] : 0;
|
||||
$b = $prevRow[$x];
|
||||
$c = ($x >= $bpp) ? $prevRow[$x - $bpp] : 0;
|
||||
|
||||
$val = match ($filter) {
|
||||
0 => $rawByte,
|
||||
1 => ($rawByte + $a) & 0xFF,
|
||||
2 => ($rawByte + $b) & 0xFF,
|
||||
3 => ($rawByte + (int)(($a + $b) / 2)) & 0xFF,
|
||||
4 => ($rawByte + self::paethPredictor($a, $b, $c)) & 0xFF,
|
||||
default => $rawByte,
|
||||
};
|
||||
|
||||
$row[] = $val;
|
||||
}
|
||||
|
||||
// Convert row to RGBA
|
||||
for ($x = 0; $x < $w; $x++) {
|
||||
if ($colorType === 6) { // RGBA
|
||||
$pixels[] = $row[$x * 4];
|
||||
$pixels[] = $row[$x * 4 + 1];
|
||||
$pixels[] = $row[$x * 4 + 2];
|
||||
$pixels[] = $row[$x * 4 + 3];
|
||||
} elseif ($colorType === 2) { // RGB
|
||||
$pixels[] = $row[$x * 3];
|
||||
$pixels[] = $row[$x * 3 + 1];
|
||||
$pixels[] = $row[$x * 3 + 2];
|
||||
$pixels[] = 255;
|
||||
} elseif ($colorType === 3 && $palette) { // Indexed
|
||||
$idx = $row[$x];
|
||||
$pixels[] = ord($palette[$idx * 3]);
|
||||
$pixels[] = ord($palette[$idx * 3 + 1]);
|
||||
$pixels[] = ord($palette[$idx * 3 + 2]);
|
||||
$pixels[] = ($trns && $idx < strlen($trns)) ? ord($trns[$idx]) : 255;
|
||||
}
|
||||
}
|
||||
|
||||
$prevRow = $row;
|
||||
}
|
||||
|
||||
return $pixels;
|
||||
}
|
||||
|
||||
private static function paethPredictor(int $a, int $b, int $c): int
|
||||
{
|
||||
$p = $a + $b - $c;
|
||||
$pa = abs($p - $a);
|
||||
$pb = abs($p - $b);
|
||||
$pc = abs($p - $c);
|
||||
if ($pa <= $pb && $pa <= $pc) return $a;
|
||||
if ($pb <= $pc) return $b;
|
||||
return $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bilinear resize of RGBA pixel array.
|
||||
*/
|
||||
private static function resizePixels(array $src, int $srcW, int $srcH, int $dstW, int $dstH): array
|
||||
{
|
||||
$dst = [];
|
||||
$xRatio = $srcW / $dstW;
|
||||
$yRatio = $srcH / $dstH;
|
||||
|
||||
for ($y = 0; $y < $dstH; $y++) {
|
||||
$srcY = $y * $yRatio;
|
||||
$y0 = (int) $srcY;
|
||||
$y1 = min($y0 + 1, $srcH - 1);
|
||||
$yFrac = $srcY - $y0;
|
||||
|
||||
for ($x = 0; $x < $dstW; $x++) {
|
||||
$srcX = $x * $xRatio;
|
||||
$x0 = (int) $srcX;
|
||||
$x1 = min($x0 + 1, $srcW - 1);
|
||||
$xFrac = $srcX - $x0;
|
||||
|
||||
for ($c = 0; $c < 4; $c++) {
|
||||
$tl = $src[($y0 * $srcW + $x0) * 4 + $c];
|
||||
$tr = $src[($y0 * $srcW + $x1) * 4 + $c];
|
||||
$bl = $src[($y1 * $srcW + $x0) * 4 + $c];
|
||||
$br = $src[($y1 * $srcW + $x1) * 4 + $c];
|
||||
|
||||
$top = $tl + ($tr - $tl) * $xFrac;
|
||||
$bot = $bl + ($br - $bl) * $xFrac;
|
||||
$dst[] = (int) round($top + ($bot - $top) * $yFrac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode RGBA pixel array to PNG binary.
|
||||
*/
|
||||
private static function encodePng(array $pixels, int $w, int $h): string
|
||||
{
|
||||
// Build raw image data with filter byte 0 (None) per row
|
||||
$raw = '';
|
||||
for ($y = 0; $y < $h; $y++) {
|
||||
$raw .= "\x00"; // filter: None
|
||||
for ($x = 0; $x < $w; $x++) {
|
||||
$i = ($y * $w + $x) * 4;
|
||||
$raw .= chr($pixels[$i]) . chr($pixels[$i + 1]) . chr($pixels[$i + 2]) . chr($pixels[$i + 3]);
|
||||
}
|
||||
}
|
||||
|
||||
$compressed = gzcompress($raw);
|
||||
|
||||
// Build PNG
|
||||
$png = "\x89PNG\r\n\x1a\n";
|
||||
|
||||
// IHDR
|
||||
$ihdr = pack('NNCCCC', $w, $h, 8, 6, 0, 0, 0); // 8-bit RGBA
|
||||
$png .= self::pngChunk('IHDR', $ihdr);
|
||||
|
||||
// IDAT
|
||||
$png .= self::pngChunk('IDAT', $compressed);
|
||||
|
||||
// IEND
|
||||
$png .= self::pngChunk('IEND', '');
|
||||
|
||||
return $png;
|
||||
}
|
||||
|
||||
private static function pngChunk(string $type, string $data): string
|
||||
{
|
||||
$chunk = $type . $data;
|
||||
return pack('N', strlen($data)) . $chunk . pack('N', crc32($chunk));
|
||||
}
|
||||
|
||||
// ── Shared Utilities ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Write ICO file from PNG data entries.
|
||||
*/
|
||||
private static function writeIco(array $entries, string $outPath): void
|
||||
{
|
||||
$count = count($entries);
|
||||
$ico = pack('vvv', 0, 1, $count);
|
||||
$offset = 6 + ($count * 16);
|
||||
$imageData = '';
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$size = $entry['size'] >= 256 ? 0 : $entry['size'];
|
||||
$dataLen = strlen($entry['data']);
|
||||
$ico .= pack('CCCCvvVV', $size, $size, 0, 0, 1, 32, $dataLen, $offset);
|
||||
$imageData .= $entry['data'];
|
||||
$offset += $dataLen;
|
||||
}
|
||||
|
||||
file_put_contents($outPath, $ico . $imageData);
|
||||
}
|
||||
|
||||
private static function generateManifest(string $outputDir): void
|
||||
{
|
||||
$manifest = [
|
||||
'icons' => [
|
||||
['src' => 'android-chrome-192x192.png', 'sizes' => '192x192', 'type' => 'image/png'],
|
||||
['src' => 'android-chrome-512x512.png', 'sizes' => '512x512', 'type' => 'image/png'],
|
||||
],
|
||||
];
|
||||
file_put_contents(
|
||||
$outputDir . '/site.webmanifest',
|
||||
json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
}
|
||||
|
||||
public static function getHeadTags(string $basePath): string
|
||||
{
|
||||
$basePath = htmlspecialchars(rtrim($basePath, '/'), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return '<link rel="apple-touch-icon" sizes="180x180" href="' . $basePath . '/apple-touch-icon.png">' . "\n"
|
||||
. '<link rel="icon" type="image/png" sizes="32x32" href="' . $basePath . '/favicon-32x32.png">' . "\n"
|
||||
. '<link rel="icon" type="image/png" sizes="16x16" href="' . $basePath . '/favicon-16x16.png">' . "\n"
|
||||
. '<link rel="manifest" href="' . $basePath . '/site.webmanifest">' . "\n"
|
||||
. '<link rel="shortcut icon" href="' . $basePath . '/favicon.ico">' . "\n";
|
||||
}
|
||||
|
||||
private static function log(string $message, string $priority = 'info'): void
|
||||
{
|
||||
$priorities = [
|
||||
'info' => Log::INFO,
|
||||
'warning' => Log::WARNING,
|
||||
'error' => Log::ERROR,
|
||||
];
|
||||
|
||||
Log::addLogger(
|
||||
['text_file' => 'mokoonyx.log.php'],
|
||||
Log::ALL,
|
||||
['mokoonyx']
|
||||
);
|
||||
|
||||
Log::add($message, $priorities[$priority] ?? Log::INFO, 'mokoonyx');
|
||||
}
|
||||
}
|
||||
1
src/helper/index.html
Normal file
1
src/helper/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
165
src/helper/minify.php
Normal file
165
src/helper/minify.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* CSS/JS minifier — generates .min files from source when dev mode is off,
|
||||
* deletes them when dev mode is on.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
class MokoMinifyHelper
|
||||
{
|
||||
/**
|
||||
* Files to minify: source path relative to template media root.
|
||||
* The .min variant is derived automatically (template.css → template.min.css).
|
||||
*/
|
||||
private const CSS_FILES = [
|
||||
'css/template.css',
|
||||
'css/offline.css',
|
||||
'css/editor.css',
|
||||
'css/a11y-high-contrast.css',
|
||||
'css/theme/light.standard.css',
|
||||
'css/theme/dark.standard.css',
|
||||
'css/theme/light.custom.css',
|
||||
'css/theme/dark.custom.css',
|
||||
];
|
||||
|
||||
private const JS_FILES = [
|
||||
'js/template.js',
|
||||
];
|
||||
|
||||
/**
|
||||
* When dev mode is ON: delete all .min files.
|
||||
* When dev mode is OFF: regenerate .min files if source is newer.
|
||||
*
|
||||
* @param string $mediaRoot Absolute path to the template media directory.
|
||||
* @param bool $devMode Whether development mode is enabled.
|
||||
*/
|
||||
public static function sync(string $mediaRoot, bool $devMode): void
|
||||
{
|
||||
$mediaRoot = rtrim($mediaRoot, '/\\');
|
||||
|
||||
foreach (self::CSS_FILES as $relPath) {
|
||||
$source = $mediaRoot . '/' . $relPath;
|
||||
$min = self::minPath($source);
|
||||
|
||||
if ($devMode) {
|
||||
self::deleteIfExists($min);
|
||||
} else {
|
||||
self::buildIfStale($source, $min, 'css');
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::JS_FILES as $relPath) {
|
||||
$source = $mediaRoot . '/' . $relPath;
|
||||
$min = self::minPath($source);
|
||||
|
||||
if ($devMode) {
|
||||
self::deleteIfExists($min);
|
||||
} else {
|
||||
self::buildIfStale($source, $min, 'js');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the .min path from a source path.
|
||||
* template.css → template.min.css
|
||||
*/
|
||||
private static function minPath(string $path): string
|
||||
{
|
||||
$info = pathinfo($path);
|
||||
return $info['dirname'] . '/' . $info['filename'] . '.min.' . $info['extension'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file if it exists.
|
||||
*/
|
||||
private static function deleteIfExists(string $path): void
|
||||
{
|
||||
if (is_file($path)) {
|
||||
@unlink($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the minified file if the source is newer or the min file is missing.
|
||||
*/
|
||||
private static function buildIfStale(string $source, string $min, string $type): void
|
||||
{
|
||||
if (!is_file($source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if min file exists and is newer than source
|
||||
if (is_file($min) && filemtime($min) >= filemtime($source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = file_get_contents($source);
|
||||
if ($content === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$minified = ($type === 'css')
|
||||
? self::minifyCss($content)
|
||||
: self::minifyJs($content);
|
||||
|
||||
file_put_contents($min, $minified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify CSS by stripping comments, excess whitespace, and unnecessary characters.
|
||||
*/
|
||||
private static function minifyCss(string $css): string
|
||||
{
|
||||
// Remove comments (but keep IE hacks like /*\*/)
|
||||
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
|
||||
|
||||
// Remove whitespace around { } : ; , > + ~
|
||||
$css = preg_replace('/\s*([{}:;,>+~])\s*/', '$1', $css);
|
||||
|
||||
// Remove remaining newlines and tabs
|
||||
$css = preg_replace('/\s+/', ' ', $css);
|
||||
|
||||
// Remove spaces around selectors
|
||||
$css = str_replace(['{ ', ' {', '; ', ' ;'], ['{', '{', ';', ';'], $css);
|
||||
|
||||
// Remove trailing semicolons before closing braces
|
||||
$css = str_replace(';}', '}', $css);
|
||||
|
||||
// Remove leading/trailing whitespace
|
||||
return trim($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify JS by stripping single-line comments, multi-line comments,
|
||||
* and collapsing whitespace. Preserves string literals.
|
||||
*/
|
||||
private static function minifyJs(string $js): string
|
||||
{
|
||||
// Remove multi-line comments
|
||||
$js = preg_replace('!/\*.*?\*/!s', '', $js);
|
||||
|
||||
// Remove single-line comments (but not URLs like http://)
|
||||
$js = preg_replace('!(?<=^|[\s;{}()\[\]])//[^\n]*!m', '', $js);
|
||||
|
||||
// Collapse whitespace
|
||||
$js = preg_replace('/\s+/', ' ', $js);
|
||||
|
||||
// Remove spaces around operators and punctuation
|
||||
$js = preg_replace('/\s*([{}();,=+\-*\/<>!&|?:])\s*/', '$1', $js);
|
||||
|
||||
// Restore necessary spaces (after keywords)
|
||||
$js = preg_replace('/(var|let|const|return|typeof|instanceof|new|delete|throw|case|in|of)([^\s;})><=!&|?:,])/', '$1 $2', $js);
|
||||
|
||||
return trim($js);
|
||||
}
|
||||
}
|
||||
76
src/html/com_content/article/index.html
Normal file
76
src/html/com_content/article/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
135
src/html/com_content/article/toc-left.php
Normal file
135
src/html/com_content/article/toc-left.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Associations;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
|
||||
// Load Bootstrap TOC assets
|
||||
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
|
||||
$wa->useStyle('vendor.bootstrap-toc');
|
||||
$wa->useScript('vendor.bootstrap-toc.js');
|
||||
|
||||
// Get article params
|
||||
$params = $this->item->params;
|
||||
$images = json_decode($this->item->images);
|
||||
$urls = json_decode($this->item->urls);
|
||||
$canEdit = $params->get('access-edit');
|
||||
$info = $params->get('info_block_position', 0);
|
||||
|
||||
// Check if associations are implemented
|
||||
$assocParam = (Associations::isEnabled() && $params->get('show_associations'));
|
||||
?>
|
||||
|
||||
<div class="com-content-article item-page<?php echo $this->pageclass_sfx; ?>">
|
||||
<div class="row">
|
||||
<!-- Table of Contents - Left Side -->
|
||||
<div class="col-lg-3 col-md-4 order-md-1 mb-4">
|
||||
<div class="sticky-top toc-wrapper" style="top: 20px;">
|
||||
<nav id="toc" data-toggle="toc" class="toc-container">
|
||||
<h5 class="toc-title"><?php echo Text::_('TPL_MOKOONYX_TOC_TITLE'); ?></h5>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Article Content -->
|
||||
<div class="col-lg-9 col-md-8 order-md-2">
|
||||
<meta itemprop="inLanguage" content="<?php echo ($this->item->language === '*') ? Factory::getApplication()->get('language') : $this->item->language; ?>" />
|
||||
|
||||
<?php if ($this->params->get('show_page_heading')) : ?>
|
||||
<div class="page-header">
|
||||
<h1><?php echo $this->escape($this->params->get('page_heading')); ?></h1>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$this->print) : ?>
|
||||
<?php if ($canEdit || $params->get('show_print_icon') || $params->get('show_email_icon')) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.icons', ['params' => $params, 'item' => $this->item, 'print' => false]); ?>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<?php if ($params->get('show_print_icon')) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.icons', ['params' => $params, 'item' => $this->item, 'print' => true]); ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php echo $this->item->event->afterDisplayTitle; ?>
|
||||
|
||||
<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php echo $this->item->event->beforeDisplayContent; ?>
|
||||
|
||||
<?php if (isset($urls) && ((!empty($urls->urls_position) && $urls->urls_position == '0') || ($params->get('urls_position') == '0' && empty($urls->urls_position))) || (empty($urls->urls_position) && (!$params->get('urls_position')))) : ?>
|
||||
<?php echo $this->loadTemplate('links'); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('access-view')) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.full_image', $this->item); ?>
|
||||
|
||||
<?php if (isset($info) && $info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'above']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="article-content" itemprop="articleBody" data-toc-scope>
|
||||
<?php echo $this->item->text; ?>
|
||||
</div>
|
||||
|
||||
<?php if (isset($urls) && ((!empty($urls->urls_position) && $urls->urls_position == '1') || ($params->get('urls_position') == '1'))) : ?>
|
||||
<?php echo $this->loadTemplate('links'); ?>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($params->get('show_noauth') == true && $this->user->get('guest')) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.intro_image', $this->item); ?>
|
||||
<?php echo HTMLHelper::_('content.prepare', $this->item->introtext); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php echo $this->item->event->afterDisplayContent; ?>
|
||||
|
||||
<?php if (isset($info) && ($info == 1 || $info == 2)) : ?>
|
||||
<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
|
||||
<?php endif; ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'below']); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.toc-container {
|
||||
background: var(--cassiopeia-color-bg, #fff);
|
||||
border: 1px solid var(--cassiopeia-color-border, #dee2e6);
|
||||
border-radius: 0.375rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.toc-title {
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--cassiopeia-color-text, #212529);
|
||||
border-bottom: 1px solid var(--cassiopeia-color-border, #dee2e6);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.toc-wrapper {
|
||||
position: static !important;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
135
src/html/com_content/article/toc-right.php
Normal file
135
src/html/com_content/article/toc-right.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Associations;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
|
||||
// Load Bootstrap TOC assets
|
||||
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
|
||||
$wa->useStyle('vendor.bootstrap-toc');
|
||||
$wa->useScript('vendor.bootstrap-toc.js');
|
||||
|
||||
// Get article params
|
||||
$params = $this->item->params;
|
||||
$images = json_decode($this->item->images);
|
||||
$urls = json_decode($this->item->urls);
|
||||
$canEdit = $params->get('access-edit');
|
||||
$info = $params->get('info_block_position', 0);
|
||||
|
||||
// Check if associations are implemented
|
||||
$assocParam = (Associations::isEnabled() && $params->get('show_associations'));
|
||||
?>
|
||||
|
||||
<div class="com-content-article item-page<?php echo $this->pageclass_sfx; ?>">
|
||||
<div class="row">
|
||||
<!-- Article Content -->
|
||||
<div class="col-lg-9 col-md-8 order-md-1">
|
||||
<meta itemprop="inLanguage" content="<?php echo ($this->item->language === '*') ? Factory::getApplication()->get('language') : $this->item->language; ?>" />
|
||||
|
||||
<?php if ($this->params->get('show_page_heading')) : ?>
|
||||
<div class="page-header">
|
||||
<h1><?php echo $this->escape($this->params->get('page_heading')); ?></h1>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$this->print) : ?>
|
||||
<?php if ($canEdit || $params->get('show_print_icon') || $params->get('show_email_icon')) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.icons', ['params' => $params, 'item' => $this->item, 'print' => false]); ?>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<?php if ($params->get('show_print_icon')) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.icons', ['params' => $params, 'item' => $this->item, 'print' => true]); ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php echo $this->item->event->afterDisplayTitle; ?>
|
||||
|
||||
<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php echo $this->item->event->beforeDisplayContent; ?>
|
||||
|
||||
<?php if (isset($urls) && ((!empty($urls->urls_position) && $urls->urls_position == '0') || ($params->get('urls_position') == '0' && empty($urls->urls_position))) || (empty($urls->urls_position) && (!$params->get('urls_position')))) : ?>
|
||||
<?php echo $this->loadTemplate('links'); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('access-view')) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.full_image', $this->item); ?>
|
||||
|
||||
<?php if (isset($info) && $info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'above']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="article-content" itemprop="articleBody" data-toc-scope>
|
||||
<?php echo $this->item->text; ?>
|
||||
</div>
|
||||
|
||||
<?php if (isset($urls) && ((!empty($urls->urls_position) && $urls->urls_position == '1') || ($params->get('urls_position') == '1'))) : ?>
|
||||
<?php echo $this->loadTemplate('links'); ?>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($params->get('show_noauth') == true && $this->user->get('guest')) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.intro_image', $this->item); ?>
|
||||
<?php echo HTMLHelper::_('content.prepare', $this->item->introtext); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php echo $this->item->event->afterDisplayContent; ?>
|
||||
|
||||
<?php if (isset($info) && ($info == 1 || $info == 2)) : ?>
|
||||
<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
|
||||
<?php endif; ?>
|
||||
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'below']); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Table of Contents - Right Side -->
|
||||
<div class="col-lg-3 col-md-4 order-md-2 mb-4">
|
||||
<div class="sticky-top toc-wrapper" style="top: 20px;">
|
||||
<nav id="toc" data-toggle="toc" class="toc-container">
|
||||
<h5 class="toc-title"><?php echo Text::_('TPL_MOKOONYX_TOC_TITLE'); ?></h5>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.toc-container {
|
||||
background: var(--cassiopeia-color-bg, #fff);
|
||||
border: 1px solid var(--cassiopeia-color-border, #dee2e6);
|
||||
border-radius: 0.375rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.toc-title {
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--cassiopeia-color-text, #212529);
|
||||
border-bottom: 1px solid var(--cassiopeia-color-border, #dee2e6);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.toc-wrapper {
|
||||
position: static !important;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
36
src/html/mod_articles_archive/default.php
Normal file
36
src/html/mod_articles_archive/default.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_archive.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-archive<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-archive__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-archive__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-archive__item">
|
||||
<a href="<?php echo $item->link; ?>"><?php echo $item->text; ?></a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_archive/index.html
Normal file
76
src/html/mod_articles_archive/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
44
src/html/mod_articles_categories/default.php
Normal file
44
src/html/mod_articles_categories/default.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_categories.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
$showDescription = $params->get('show_description', 0);
|
||||
$numitems = $params->get('numitems', 0);
|
||||
?>
|
||||
<div class="mod-articles-categories<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-categories__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-categories__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-categories__item">
|
||||
<a href="<?php echo $item->link; ?>"><?php echo $item->title; ?></a>
|
||||
<?php if ($numitems) : ?>
|
||||
<span class="mod-articles-categories__count">(<?php echo $item->numitems; ?>)</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($showDescription && !empty($item->description)) : ?>
|
||||
<p class="mod-articles-categories__description"><?php echo $item->description; ?></p>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_categories/index.html
Normal file
76
src/html/mod_articles_categories/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
78
src/html/mod_articles_category/default.php
Normal file
78
src/html/mod_articles_category/default.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_category.
|
||||
* Adds showtitle support and respects module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
Factory::getApplication()->getLanguage()->load('mod_articles_category', JPATH_SITE);
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-category<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-category__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-category__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-category__item" itemscope itemtype="https://schema.org/Article">
|
||||
<?php if ($params->get('link_titles') == 1) : ?>
|
||||
<a class="mod-articles-category__link" href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<span itemprop="name"><?php echo $item->title; ?></span>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span itemprop="name"><?php echo $item->title; ?></span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->displayHits) : ?>
|
||||
<span class="mod-articles-category__hits">
|
||||
(<?php echo $item->displayHits; ?>)
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_author', 0)) : ?>
|
||||
<span class="mod-articles-category__author">
|
||||
<?php echo $item->displayAuthorName; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->displayDate) : ?>
|
||||
<time class="mod-articles-category__date" datetime="<?php echo HTMLHelper::_('date', $item->displayDate, 'c'); ?>" itemprop="datePublished">
|
||||
<?php echo $item->displayDate; ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_introtext', 0)) : ?>
|
||||
<div class="mod-articles-category__intro" itemprop="description">
|
||||
<?php echo $item->displayIntrotext; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_readmore', 0)) : ?>
|
||||
<a class="mod-articles-category__readmore" href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<?php echo Text::_('MOD_ARTICLES_CATEGORY_READ_MORE_TITLE'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_category/index.html
Normal file
76
src/html/mod_articles_category/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
40
src/html/mod_articles_latest/default.php
Normal file
40
src/html/mod_articles_latest/default.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_latest.
|
||||
* Adds showtitle support and respects module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-latest<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-latest__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-latest__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-latest__item" itemscope itemtype="https://schema.org/Article">
|
||||
<a href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<span itemprop="name"><?php echo $item->title; ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_latest/index.html
Normal file
76
src/html/mod_articles_latest/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
58
src/html/mod_articles_news/default.php
Normal file
58
src/html/mod_articles_news/default.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_news (newsflash).
|
||||
* Adds showtitle support with card-based layout.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-news newsflash<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-news__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<div class="mod-articles-news__item" itemscope itemtype="https://schema.org/Article">
|
||||
<?php if ($params->get('item_title')) : ?>
|
||||
<h4 class="mod-articles-news__item-title" itemprop="name">
|
||||
<?php if ($item->link !== '' && $params->get('link_titles')) : ?>
|
||||
<a href="<?php echo $item->link; ?>" itemprop="url"><?php echo $item->title; ?></a>
|
||||
<?php else : ?>
|
||||
<?php echo $item->title; ?>
|
||||
<?php endif; ?>
|
||||
</h4>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($item->afterDisplayTitle)) : ?>
|
||||
<?php echo $item->afterDisplayTitle; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_introtext', 1)) : ?>
|
||||
<div class="mod-articles-news__intro" itemprop="description">
|
||||
<?php echo $item->introtext; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($item->readmore) && $item->readmore) : ?>
|
||||
<a class="mod-articles-news__readmore" href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<?php echo $item->linkText; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
76
src/html/mod_articles_news/index.html
Normal file
76
src/html/mod_articles_news/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
38
src/html/mod_articles_popular/default.php
Normal file
38
src/html/mod_articles_popular/default.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_popular.
|
||||
* Adds showtitle support and respects module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-popular<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-popular__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-popular__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-popular__item" itemscope itemtype="https://schema.org/Article">
|
||||
<a href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<span itemprop="name"><?php echo $item->title; ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_popular/index.html
Normal file
76
src/html/mod_articles_popular/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
52
src/html/mod_banners/default.php
Normal file
52
src/html/mod_banners/default.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_banners.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-banners<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-banners__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<div class="mod-banners__item">
|
||||
<?php $link = $item->params->get('url') ?: ''; ?>
|
||||
<?php if ($item->type == 1) : ?>
|
||||
<?php // Image banner ?>
|
||||
<?php $imageUrl = $item->params->get('imageurl', ''); ?>
|
||||
<?php $alt = htmlspecialchars($item->name, ENT_COMPAT, 'UTF-8'); ?>
|
||||
<?php if ($link) : ?>
|
||||
<a href="<?php echo htmlspecialchars($link, ENT_COMPAT, 'UTF-8'); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<img src="<?php echo htmlspecialchars($imageUrl, ENT_COMPAT, 'UTF-8'); ?>" alt="<?php echo $alt; ?>" class="mod-banners__image" loading="lazy" />
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<img src="<?php echo htmlspecialchars($imageUrl, ENT_COMPAT, 'UTF-8'); ?>" alt="<?php echo $alt; ?>" class="mod-banners__image" loading="lazy" />
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<?php // Custom HTML banner ?>
|
||||
<?php echo $item->custombannercode; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
76
src/html/mod_banners/index.html
Normal file
76
src/html/mod_banners/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
53
src/html/mod_breadcrumbs/default.php
Normal file
53
src/html/mod_breadcrumbs/default.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_breadcrumbs.
|
||||
* Bootstrap 5 breadcrumb with schema.org BreadcrumbList markup.
|
||||
* Module settings (showHome, showLast, homeText) are handled by Joomla core
|
||||
* before $list reaches this template.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
$showHere = $params->get('showHere', 1);
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<nav class="mod-breadcrumbs<?php echo $suffix ? ' ' . $suffix : ''; ?>" aria-label="<?php echo Text::_('MOD_BREADCRUMBS_HERE'); ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-breadcrumbs__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<?php if ($showHere) : ?>
|
||||
<span class="mod-breadcrumbs__here"><?php echo Text::_('MOD_BREADCRUMBS_HERE'); ?></span>
|
||||
<?php endif; ?>
|
||||
<ol class="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
|
||||
<?php foreach ($list as $key => $item) : ?>
|
||||
<?php $isLast = ($key === array_key_last($list)); ?>
|
||||
<li class="breadcrumb-item<?php echo $isLast ? ' active' : ''; ?>" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"
|
||||
<?php echo $isLast ? ' aria-current="page"' : ''; ?>>
|
||||
<?php if (!$isLast && !empty($item->link)) : ?>
|
||||
<a href="<?php echo $item->link; ?>" itemprop="item">
|
||||
<span itemprop="name"><?php echo $item->name; ?></span>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span itemprop="name"><?php echo $item->name; ?></span>
|
||||
<?php endif; ?>
|
||||
<meta itemprop="position" content="<?php echo $key + 1; ?>" />
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
</nav>
|
||||
76
src/html/mod_breadcrumbs/index.html
Normal file
76
src/html/mod_breadcrumbs/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
39
src/html/mod_custom/default.php
Normal file
39
src/html/mod_custom/default.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_custom.
|
||||
* Adds showtitle support and respects all module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
$modId = 'mod-custom' . $module->id;
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
if ($params->get('backgroundimage')) {
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $app->getDocument()->getWebAssetManager();
|
||||
$wa->addInlineStyle(
|
||||
'#' . $modId . '{background-image: url("' . Uri::root(true) . '/' . HTMLHelper::_('cleanImageURL', $params->get('backgroundimage'))->url . '");}',
|
||||
['name' => $modId]
|
||||
);
|
||||
}
|
||||
?>
|
||||
<div class="mod-custom custom<?php echo $suffix ? ' ' . $suffix : ''; ?>" id="<?php echo $modId; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-custom__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<?php echo $module->content; ?>
|
||||
</div>
|
||||
42
src/html/mod_custom/hero.php
Normal file
42
src/html/mod_custom/hero.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Template override for mod_custom adding banner-overlay wrapper pattern.
|
||||
* Based on Cassiopeia's banner layout approach.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
$modId = 'mod-custom' . $module->id;
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
if ($params->get('backgroundimage')) {
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $app->getDocument()->getWebAssetManager();
|
||||
$wa->addInlineStyle(
|
||||
'#' . $modId . '{background-image: url("' . Uri::root(true) . '/' . HTMLHelper::_('cleanImageURL', $params->get('backgroundimage'))->url . '");}',
|
||||
['name' => $modId]
|
||||
);
|
||||
}
|
||||
?>
|
||||
<div class="mod-custom custom banner-overlay custom-hero<?php echo $suffix ? ' ' . $suffix : ''; ?>" id="<?php echo $modId; ?>">
|
||||
<div class="overlay">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-custom__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<?php echo $module->content; ?>
|
||||
</div>
|
||||
</div>
|
||||
88
src/html/mod_feed/default.php
Normal file
88
src/html/mod_feed/default.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_feed.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
|
||||
if (!$feed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
$rssurl = $params->get('rssurl', '');
|
||||
$rsstitle = $params->get('rsstitle', 1);
|
||||
$rssdesc = $params->get('rssrtl', 0) ? ' feed-rtl' : '';
|
||||
$rssimage = $params->get('rssimage', 1);
|
||||
$rssitems = $params->get('rssitems', 5);
|
||||
$rssitemdesc = $params->get('rssitemdesc', 1);
|
||||
$word_count = $params->get('word_count', 0);
|
||||
?>
|
||||
<div class="mod-feed<?php echo $suffix ? ' ' . $suffix : ''; ?><?php echo $rssdesc; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-feed__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($feed->title && $rsstitle) : ?>
|
||||
<h4 class="mod-feed__feed-title">
|
||||
<?php if (!empty($rssurl)) : ?>
|
||||
<a href="<?php echo htmlspecialchars($rssurl, ENT_COMPAT, 'UTF-8'); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo $feed->title; ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<?php echo $feed->title; ?>
|
||||
<?php endif; ?>
|
||||
</h4>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($feed->description && $rssdesc) : ?>
|
||||
<p class="mod-feed__description"><?php echo $feed->description; ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($rssimage && $feed->image) : ?>
|
||||
<img src="<?php echo $feed->image->uri; ?>" alt="<?php echo $feed->image->title ?? ''; ?>" class="mod-feed__image" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($feed->items)) : ?>
|
||||
<ul class="mod-feed__list">
|
||||
<?php for ($i = 0, $max = min(count($feed->items), $rssitems); $i < $max; $i++) :
|
||||
$item = $feed->items[$i];
|
||||
?>
|
||||
<li class="mod-feed__item">
|
||||
<?php if (!empty($item->uri)) : ?>
|
||||
<a href="<?php echo htmlspecialchars($item->uri, ENT_COMPAT, 'UTF-8'); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo $item->title; ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<?php echo $item->title; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($rssitemdesc && !empty($item->content)) :
|
||||
$desc = $item->content;
|
||||
if ($word_count) {
|
||||
$words = explode(' ', strip_tags($desc));
|
||||
if (count($words) > $word_count) {
|
||||
$desc = implode(' ', array_slice($words, 0, $word_count)) . '…';
|
||||
}
|
||||
}
|
||||
?>
|
||||
<p class="mod-feed__item-description"><?php echo $desc; ?></p>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
76
src/html/mod_feed/index.html
Normal file
76
src/html/mod_feed/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
85
src/html/mod_finder/default.php
Normal file
85
src/html/mod_finder/default.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_finder (Smart Search).
|
||||
* Bootstrap 5 search form with showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
// Load component language for search labels
|
||||
$lang = $app->getLanguage();
|
||||
$lang->load('com_finder', JPATH_SITE);
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
$showLabel = $params->get('show_label', 1);
|
||||
$labelClass = (!$showLabel ? 'visually-hidden ' : '') . 'finder';
|
||||
|
||||
Text::script('MOD_FINDER_SEARCH_VALUE');
|
||||
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $app->getDocument()->getWebAssetManager();
|
||||
$wa->getRegistry()->addExtensionRegistryFile('com_finder');
|
||||
|
||||
if ($params->get('show_autosuggest', 1)) {
|
||||
$wa->usePreset('awesomplete');
|
||||
$app->getDocument()->addScriptOptions('finder-search', ['url' => Route::_('index.php?option=com_finder&task=suggestions.suggest&format=json&tmpl=component', false)]);
|
||||
Text::script('COM_FINDER_SEARCH_FORM_LIST_LABEL');
|
||||
Text::script('JLIB_JS_AJAX_ERROR_OTHER');
|
||||
Text::script('JLIB_JS_AJAX_ERROR_PARSE');
|
||||
}
|
||||
|
||||
$wa->useScript('com_finder.finder');
|
||||
?>
|
||||
<div class="mod-finder<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-finder__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<form class="mod-finder__form js-finder-searchform form-search" action="<?php echo Route::_($route); ?>" method="get" role="search">
|
||||
<label for="mod-finder-searchword<?php echo $module->id; ?>" class="<?php echo $labelClass; ?>">
|
||||
<?php echo $params->get('alt_label', Text::_('JSEARCH_FILTER_SUBMIT')); ?>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" id="mod-finder-searchword<?php echo $module->id; ?>"
|
||||
class="js-finder-search-query form-control"
|
||||
value="<?php echo htmlspecialchars($app->getInput()->get('q', '', 'string'), ENT_COMPAT, 'UTF-8'); ?>"
|
||||
placeholder="<?php echo Text::_('MOD_FINDER_SEARCH_VALUE'); ?>">
|
||||
<?php if ($params->get('show_button', 0)) : ?>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<span class="fa-solid fa-magnifying-glass" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_('JSEARCH_FILTER_SUBMIT'); ?></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php $show_advanced = $params->get('show_advanced', 0); ?>
|
||||
<?php if ($show_advanced == 2) : ?>
|
||||
<a href="<?php echo Route::_($route); ?>" class="mod-finder__advanced-link mt-2 d-inline-block">
|
||||
<?php echo Text::_('COM_FINDER_ADVANCED_SEARCH'); ?>
|
||||
</a>
|
||||
<?php elseif ($show_advanced == 1) : ?>
|
||||
<div class="mod-finder__advanced js-finder-advanced mt-2">
|
||||
<?php echo HTMLHelper::_('filter.select', $query, $params); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$finderHelper = $app->bootModule('mod_finder', 'site')->getHelper('FinderHelper');
|
||||
echo $finderHelper->getHiddenFields($route);
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
76
src/html/mod_finder/index.html
Normal file
76
src/html/mod_finder/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
29
src/html/mod_footer/default.php
Normal file
29
src/html/mod_footer/default.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_footer.
|
||||
* Adds showtitle support and respects module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-footer<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-footer__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<div class="mod-footer__line1"><?php echo $lineone; ?></div>
|
||||
<div class="mod-footer__line2"><?php echo Text::_('MOD_FOOTER_LINE2'); ?></div>
|
||||
</div>
|
||||
76
src/html/mod_footer/index.html
Normal file
76
src/html/mod_footer/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
63
src/html/mod_languages/default.php
Normal file
63
src/html/mod_languages/default.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_languages.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-languages<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-languages__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-languages__list">
|
||||
<?php foreach ($list as $language) : ?>
|
||||
<?php $isActive = $language->active ? ' active' : ''; ?>
|
||||
<li class="mod-languages__item<?php echo $isActive; ?>" dir="<?php echo $language->rtl ? 'rtl' : 'ltr'; ?>">
|
||||
<?php if ($language->active) : ?>
|
||||
<span class="mod-languages__link mod-languages__link--active" lang="<?php echo $language->sef; ?>">
|
||||
<?php else : ?>
|
||||
<a class="mod-languages__link" href="<?php echo htmlspecialchars($language->link, ENT_COMPAT, 'UTF-8'); ?>" lang="<?php echo $language->sef; ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('image', 1)) : ?>
|
||||
<?php if ($language->image) : ?>
|
||||
<?php echo HTMLHelper::_('image', 'mod_languages/' . $language->image . '.gif', '', null, true); ?>
|
||||
<?php else : ?>
|
||||
<span class="mod-languages__badge badge bg-secondary"><?php echo strtoupper($language->sef); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_name', 1)) : ?>
|
||||
<?php echo $language->title_native; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($language->active) : ?>
|
||||
</span>
|
||||
<?php else : ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_languages/index.html
Normal file
76
src/html/mod_languages/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
126
src/html/mod_login/default.php
Normal file
126
src/html/mod_login/default.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_login.
|
||||
* Bootstrap 5 login form with showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
Factory::getApplication()->getLanguage()->load('mod_login', JPATH_SITE);
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-login<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-login__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($type === 'logout') : ?>
|
||||
<form action="<?php echo Route::_('index.php', true); ?>" method="post" class="mod-login__form mod-login__form--logout">
|
||||
<?php if ($params->get('greeting', 1)) : ?>
|
||||
<div class="mod-login__greeting">
|
||||
<?php if (!empty($user->name)) : ?>
|
||||
<span class="mod-login__name"><?php echo Text::sprintf('MOD_LOGIN_HINAME', htmlspecialchars($user->name, ENT_COMPAT, 'UTF-8')); ?></span>
|
||||
<?php else : ?>
|
||||
<span class="mod-login__name"><?php echo Text::sprintf('MOD_LOGIN_HINAME', htmlspecialchars($user->username, ENT_COMPAT, 'UTF-8')); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="mod-login__submit">
|
||||
<button type="submit" name="Submit" class="btn btn-primary w-100"><?php echo Text::_('JLOGOUT'); ?></button>
|
||||
</div>
|
||||
<input type="hidden" name="option" value="com_users">
|
||||
<input type="hidden" name="task" value="user.logout">
|
||||
<input type="hidden" name="return" value="<?php echo $return; ?>">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</form>
|
||||
<?php else : ?>
|
||||
<form action="<?php echo Route::_('index.php', true); ?>" method="post" class="mod-login__form mod-login__form--login">
|
||||
<?php if ($params->get('pretext')) : ?>
|
||||
<div class="mod-login__pretext"><?php echo $params->get('pretext'); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-login__field mb-3">
|
||||
<label for="modlgn-username-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_USERNAME'); ?></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa-solid fa-user" aria-hidden="true"></i></span>
|
||||
<input id="modlgn-username-<?php echo $module->id; ?>" type="text" name="username" class="form-control" autocomplete="username" placeholder="<?php echo Text::_('JGLOBAL_USERNAME'); ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mod-login__field mb-3">
|
||||
<label for="modlgn-passwd-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_PASSWORD'); ?></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa-solid fa-lock" aria-hidden="true"></i></span>
|
||||
<input id="modlgn-passwd-<?php echo $module->id; ?>" type="password" name="password" class="form-control" autocomplete="current-password" placeholder="<?php echo Text::_('JGLOBAL_PASSWORD'); ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($twofactormethods) && count($twofactormethods) > 1) : ?>
|
||||
<div class="mod-login__field mb-3">
|
||||
<label for="modlgn-secretkey-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_SECRETKEY'); ?></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa-solid fa-shield-halved" aria-hidden="true"></i></span>
|
||||
<input id="modlgn-secretkey-<?php echo $module->id; ?>" type="text" name="secretkey" class="form-control" autocomplete="one-time-code" placeholder="<?php echo Text::_('JGLOBAL_SECRETKEY'); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('remember', 1)) : ?>
|
||||
<div class="mod-login__remember form-check mb-3">
|
||||
<input id="modlgn-remember-<?php echo $module->id; ?>" type="checkbox" name="remember" class="form-check-input" value="yes">
|
||||
<label for="modlgn-remember-<?php echo $module->id; ?>" class="form-check-label"><?php echo Text::_('JGLOBAL_REMEMBER_ME'); ?></label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-login__submit mb-3">
|
||||
<button type="submit" name="Submit" class="btn btn-primary w-100"><?php echo Text::_('JLOGIN'); ?></button>
|
||||
</div>
|
||||
|
||||
<?php $usersConfig = \Joomla\CMS\Component\ComponentHelper::getParams('com_users'); ?>
|
||||
<ul class="mod-login__options list-unstyled small">
|
||||
<?php if ($usersConfig->get('allowUserRegistration')) : ?>
|
||||
<li>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&view=registration'); ?>">
|
||||
<?php echo Text::_('MOD_LOGIN_REGISTER'); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<li>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&view=remind'); ?>">
|
||||
<?php echo Text::_('MOD_LOGIN_FORGOT_YOUR_USERNAME'); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&view=reset'); ?>">
|
||||
<?php echo Text::_('MOD_LOGIN_FORGOT_YOUR_PASSWORD'); ?>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<input type="hidden" name="option" value="com_users">
|
||||
<input type="hidden" name="task" value="user.login">
|
||||
<input type="hidden" name="return" value="<?php echo $return; ?>">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
|
||||
<?php if ($params->get('posttext')) : ?>
|
||||
<div class="mod-login__posttext"><?php echo $params->get('posttext'); ?></div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
76
src/html/mod_login/index.html
Normal file
76
src/html/mod_login/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
95
src/html/mod_menu/default.php
Normal file
95
src/html/mod_menu/default.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_menu.
|
||||
* Simple list menu with showtitle support, suitable for sidebars and footers.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Helper\ModuleHelper;
|
||||
|
||||
$id = '';
|
||||
|
||||
if ($tagId = $params->get('tag_id', '')) {
|
||||
$id = ' id="' . $tagId . '"';
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<nav class="mod-menu<?php echo $suffix ? ' ' . $suffix : ''; ?>"<?php echo $id; ?> aria-label="<?php echo htmlspecialchars($module->title, ENT_COMPAT, 'UTF-8'); ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-menu__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-menu__list nav flex-column">
|
||||
<?php foreach ($list as $i => &$item) :
|
||||
$itemParams = $item->getParams();
|
||||
$class = 'nav-item mod-menu__item item-' . $item->id;
|
||||
|
||||
if ($item->id == $default_id) {
|
||||
$class .= ' default';
|
||||
}
|
||||
|
||||
if ($item->id == $active_id || ($item->type === 'alias' && $itemParams->get('aliasoptions') == $active_id)) {
|
||||
$class .= ' current';
|
||||
}
|
||||
|
||||
if (in_array($item->id, $path)) {
|
||||
$class .= ' active';
|
||||
} elseif ($item->type === 'alias') {
|
||||
$aliasToId = $itemParams->get('aliasoptions');
|
||||
|
||||
if (count($path) > 0 && $aliasToId == $path[count($path) - 1]) {
|
||||
$class .= ' active';
|
||||
} elseif (in_array($aliasToId, $path)) {
|
||||
$class .= ' alias-parent-active';
|
||||
}
|
||||
}
|
||||
|
||||
if ($item->type === 'separator') {
|
||||
$class .= ' divider';
|
||||
}
|
||||
|
||||
if ($item->deeper) {
|
||||
$class .= ' deeper';
|
||||
}
|
||||
|
||||
if ($item->parent) {
|
||||
$class .= ' parent';
|
||||
}
|
||||
|
||||
echo '<li class="' . $class . '">';
|
||||
|
||||
switch ($item->type) :
|
||||
case 'separator':
|
||||
case 'component':
|
||||
case 'heading':
|
||||
case 'url':
|
||||
require ModuleHelper::getLayoutPath('mod_menu', 'default_' . $item->type);
|
||||
break;
|
||||
|
||||
default:
|
||||
require ModuleHelper::getLayoutPath('mod_menu', 'default_url');
|
||||
break;
|
||||
endswitch;
|
||||
|
||||
if ($item->deeper) {
|
||||
echo '<ul class="mod-menu__sub nav flex-column ms-3">';
|
||||
} elseif ($item->shallower) {
|
||||
echo '</li>';
|
||||
echo str_repeat('</ul></li>', $item->level_diff);
|
||||
} else {
|
||||
echo '</li>';
|
||||
}
|
||||
endforeach;
|
||||
?></ul>
|
||||
</nav>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user