From ec75322bedbbfa84ce4c532c0a789bfc5aecaf4c Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Thu, 4 Jun 2026 14:03:28 +0000
Subject: [PATCH 01/11] chore: sync updates.xml 02.20.00 from main [skip ci]
---
updates.xml | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/updates.xml b/updates.xml
index 8d493ff..e4d4b6d 100644
--- a/updates.xml
+++ b/updates.xml
@@ -1,7 +1,7 @@
@@ -13,16 +13,16 @@
site
02.19.02-dev
2026-06-04
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development
+ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/tpl_mokoonyx-02.19.02-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/tpl_mokoonyx-02.19.02-dev.zip
5811cc175564744865053f5a2cf259e66016c4e41f2d87571d19b1dd5f7fd8ba
dev
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/CHANGELOG.md
Moko Consulting
https://mokoconsulting.tech
-
+
8.1.0
@@ -31,18 +31,18 @@
mokoonyx
template
site
- 02.19.00
+ 02.20.00
2026-06-04
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable
+ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.19.00.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.20.00.zip
- 1b4907b450be64d1d23ea0da4314e542e6abf2c9e6e2085ad4b2e49079bd1d83
+ c1a5c28f7a74da65b8f303bd72892e241e2ff5229a2f47dee744b3eb5e7a9163
stable
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/CHANGELOG.md
Moko Consulting
https://mokoconsulting.tech
-
+
8.1.0
--
2.52.0
From b1391c6d035c717c79b9c65ac6166cc2ed1b5123 Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Thu, 4 Jun 2026 14:20:22 +0000
Subject: [PATCH 02/11] chore: sync .mokogitea/workflows/auto-release.yml from
moko-platform [skip ci]
---
.mokogitea/workflows/auto-release.yml | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml
index 2325032..44a2d64 100644
--- a/.mokogitea/workflows/auto-release.yml
+++ b/.mokogitea/workflows/auto-release.yml
@@ -102,13 +102,14 @@ jobs:
run: |
php /tmp/moko-platform-api/cli/release_publish.php \
--path . --stability rc --bump minor --branch rc \
- --token "${{ secrets.MOKOGITEA_TOKEN }}"
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
+ --skip-update-stream
- name: Summary
if: always()
run: |
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
- echo "Branch renamed to rc, minor bump, RC + lesser stream releases built, updates.xml synced" >> $GITHUB_STEP_SUMMARY
+ echo "Branch renamed to rc, minor bump, RC release built (updates.xml managed by Gitea Pages)" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
release:
@@ -167,7 +168,8 @@ jobs:
run: |
php /tmp/moko-platform-api/cli/release_publish.php \
--path . --stability stable --bump minor --branch main \
- --token "${{ secrets.MOKOGITEA_TOKEN }}"
+ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
+ --skip-update-stream
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub"
--
2.52.0
From 0cdd186eae0b04ad1414985f234f345dca3d6a77 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Thu, 4 Jun 2026 09:50:11 -0500
Subject: [PATCH 03/11] fix(menu): strip Joomla-injected p-2 padding from FA
icon classes
Joomla's admin stores Bootstrap padding utility classes (p-0 through
p-5) in menu_icon alongside the Font Awesome class. This makes icon
padding too small. Strip these classes before rendering in all 12
menu override files (default, mainmenu, horizontal variants).
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/html/mod_menu/default_component.php | 2 ++
src/html/mod_menu/default_heading.php | 2 ++
src/html/mod_menu/default_separator.php | 2 ++
src/html/mod_menu/default_url.php | 2 ++
src/html/mod_menu/horizontal_component.php | 2 ++
src/html/mod_menu/horizontal_heading.php | 2 ++
src/html/mod_menu/horizontal_separator.php | 2 ++
src/html/mod_menu/horizontal_url.php | 2 ++
src/html/mod_menu/mainmenu_component.php | 2 ++
src/html/mod_menu/mainmenu_heading.php | 2 ++
src/html/mod_menu/mainmenu_separator.php | 2 ++
src/html/mod_menu/mainmenu_url.php | 2 ++
12 files changed, 24 insertions(+)
diff --git a/src/html/mod_menu/default_component.php b/src/html/mod_menu/default_component.php
index 2242c7f..9568e3f 100644
--- a/src/html/mod_menu/default_component.php
+++ b/src/html/mod_menu/default_component.php
@@ -33,6 +33,8 @@ if ($item->anchor_rel) {
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
if ($itemParams->get('menu_text', 1)) {
$linktype = '' . $item->title;
} else {
diff --git a/src/html/mod_menu/default_heading.php b/src/html/mod_menu/default_heading.php
index eeaaf2e..60368f5 100644
--- a/src/html/mod_menu/default_heading.php
+++ b/src/html/mod_menu/default_heading.php
@@ -19,6 +19,8 @@ $anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
if ($itemParams->get('menu_text', 1)) {
$linktype = '' . $item->title;
} else {
diff --git a/src/html/mod_menu/default_separator.php b/src/html/mod_menu/default_separator.php
index 1a08dbc..7eb600c 100644
--- a/src/html/mod_menu/default_separator.php
+++ b/src/html/mod_menu/default_separator.php
@@ -19,6 +19,8 @@ $anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
if ($itemParams->get('menu_text', 1)) {
$linktype = '' . $item->title;
} else {
diff --git a/src/html/mod_menu/default_url.php b/src/html/mod_menu/default_url.php
index 55b6eab..affe564 100644
--- a/src/html/mod_menu/default_url.php
+++ b/src/html/mod_menu/default_url.php
@@ -33,6 +33,8 @@ if ($item->anchor_rel) {
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
if ($itemParams->get('menu_text', 1)) {
$linktype = '' . $item->title;
} else {
diff --git a/src/html/mod_menu/horizontal_component.php b/src/html/mod_menu/horizontal_component.php
index 7619390..30c76d2 100644
--- a/src/html/mod_menu/horizontal_component.php
+++ b/src/html/mod_menu/horizontal_component.php
@@ -33,6 +33,8 @@ if ($item->anchor_rel) {
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
if ($itemParams->get('menu_text', 1)) {
$linktype = '' . $item->title;
} else {
diff --git a/src/html/mod_menu/horizontal_heading.php b/src/html/mod_menu/horizontal_heading.php
index 2bc20ef..fe559a9 100644
--- a/src/html/mod_menu/horizontal_heading.php
+++ b/src/html/mod_menu/horizontal_heading.php
@@ -19,6 +19,8 @@ $anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
if ($itemParams->get('menu_text', 1)) {
$linktype = '' . $item->title;
} else {
diff --git a/src/html/mod_menu/horizontal_separator.php b/src/html/mod_menu/horizontal_separator.php
index d7625b6..ed469f1 100644
--- a/src/html/mod_menu/horizontal_separator.php
+++ b/src/html/mod_menu/horizontal_separator.php
@@ -19,6 +19,8 @@ $anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
if ($itemParams->get('menu_text', 1)) {
$linktype = '' . $item->title;
} else {
diff --git a/src/html/mod_menu/horizontal_url.php b/src/html/mod_menu/horizontal_url.php
index 2ed318d..caa2cc4 100644
--- a/src/html/mod_menu/horizontal_url.php
+++ b/src/html/mod_menu/horizontal_url.php
@@ -33,6 +33,8 @@ if ($item->anchor_rel) {
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
if ($itemParams->get('menu_text', 1)) {
$linktype = '' . $item->title;
} else {
diff --git a/src/html/mod_menu/mainmenu_component.php b/src/html/mod_menu/mainmenu_component.php
index d2887cb..6f0cc07 100644
--- a/src/html/mod_menu/mainmenu_component.php
+++ b/src/html/mod_menu/mainmenu_component.php
@@ -33,6 +33,8 @@ if ($item->anchor_rel) {
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
diff --git a/src/html/mod_menu/mainmenu_heading.php b/src/html/mod_menu/mainmenu_heading.php
index af61674..b1c2b50 100644
--- a/src/html/mod_menu/mainmenu_heading.php
+++ b/src/html/mod_menu/mainmenu_heading.php
@@ -19,6 +19,8 @@ $anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
diff --git a/src/html/mod_menu/mainmenu_separator.php b/src/html/mod_menu/mainmenu_separator.php
index 42579b9..59d0c58 100644
--- a/src/html/mod_menu/mainmenu_separator.php
+++ b/src/html/mod_menu/mainmenu_separator.php
@@ -19,6 +19,8 @@ $anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
diff --git a/src/html/mod_menu/mainmenu_url.php b/src/html/mod_menu/mainmenu_url.php
index 1d81553..e537255 100644
--- a/src/html/mod_menu/mainmenu_url.php
+++ b/src/html/mod_menu/mainmenu_url.php
@@ -33,6 +33,8 @@ if ($item->anchor_rel) {
$linktype = $item->title;
if ($item->menu_icon) {
+ // Strip Joomla-injected padding classes that conflict with FA icon sizing
+ $item->menu_icon = trim(preg_replace('/\bp-[0-5]\b/', '', $item->menu_icon));
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
--
2.52.0
From 5a9e8d86a11de442a0a155b965eaea4a65de0b65 Mon Sep 17 00:00:00 2001
From: "gitea-actions[bot]"
Date: Thu, 4 Jun 2026 14:50:30 +0000
Subject: [PATCH 04/11] chore(version): auto-bump 02.19.03-dev [skip ci]
---
.mokogitea/manifest.xml | 2 +-
.mokogitea/workflows/issue-branch.yml | 2 +-
CHANGELOG.md | 4 ++--
SECURITY.md | 2 +-
src/html/layouts/joomla/module/card.php | 2 +-
src/html/layouts/mokoonyx/article-metadata.php | 2 +-
src/media/css/a11y-high-contrast.css | 2 +-
src/templateDetails.xml | 2 +-
updates.xml | 2 +-
9 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml
index 8891b59..be72701 100644
--- a/.mokogitea/manifest.xml
+++ b/.mokogitea/manifest.xml
@@ -9,7 +9,7 @@
Template - MokoOnyx
MokoConsulting
MokoOnyx - Joomla site template (successor to MokoCassiopeia)
- 02.19.02
+ 02.19.03
GNU General Public License v3
diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml
index f450d94..0839499 100644
--- a/.mokogitea/workflows/issue-branch.yml
+++ b/.mokogitea/workflows/issue-branch.yml
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
-# VERSION: 02.19.02
+# VERSION: 02.19.03
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6226b5b..2600f8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,11 +8,11 @@
DEFGROUP: Joomla.Template.Site
INGROUP: MokoOnyx.Documentation
PATH: ./CHANGELOG.md
- VERSION: 02.19.02
+ VERSION: 02.19.03
BRIEF: Changelog file documenting version history of MokoOnyx
-->
-# Changelog — MokoOnyx (VERSION: 02.19.02)
+# Changelog — MokoOnyx (VERSION: 02.19.03)
## [Unreleased]
## [02.19.00] --- 2026-06-04
diff --git a/SECURITY.md b/SECURITY.md
index d707c57..5fae7f7 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -10,7 +10,7 @@
INGROUP: MokoOnyx.Governance
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
FILE: SECURITY.md
- VERSION: 02.19.02
+ VERSION: 02.19.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.
diff --git a/src/html/layouts/joomla/module/card.php b/src/html/layouts/joomla/module/card.php
index 807b96b..566ab1a 100644
--- a/src/html/layouts/joomla/module/card.php
+++ b/src/html/layouts/joomla/module/card.php
@@ -10,7 +10,7 @@
* INGROUP: MokoOnyx
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: /html/layouts/joomla/module/card.php
- * VERSION: 02.19.02
+ * VERSION: 02.19.03
* BRIEF: Custom card module chrome — renders module titles for all modules
*/
diff --git a/src/html/layouts/mokoonyx/article-metadata.php b/src/html/layouts/mokoonyx/article-metadata.php
index 2e21eee..d95431f 100644
--- a/src/html/layouts/mokoonyx/article-metadata.php
+++ b/src/html/layouts/mokoonyx/article-metadata.php
@@ -11,7 +11,7 @@
* INGROUP: MokoOnyx.Layouts
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: /src/html/layouts/mokoonyx/article-metadata.php
- * VERSION: 02.19.02
+ * VERSION: 02.19.03
* BRIEF: Article metadata footer layout -- renders jcfields grouped by field group
*/
diff --git a/src/media/css/a11y-high-contrast.css b/src/media/css/a11y-high-contrast.css
index a7a5503..81e0b96 100644
--- a/src/media/css/a11y-high-contrast.css
+++ b/src/media/css/a11y-high-contrast.css
@@ -10,7 +10,7 @@
* INGROUP: MokoOnyx.Accessibility
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: ./media/css/a11y-high-contrast.css
- * VERSION: 02.19.02
+ * VERSION: 02.19.03
* BRIEF: High-contrast stylesheet for accessibility toolbar
*/
diff --git a/src/templateDetails.xml b/src/templateDetails.xml
index 02d4690..bcd11a5 100644
--- a/src/templateDetails.xml
+++ b/src/templateDetails.xml
@@ -36,7 +36,7 @@
mokoonyx
- 02.19.02-dev
+ 02.19.03-dev
script.php
2026-05-16
Jonathan Miller || Moko Consulting
diff --git a/updates.xml b/updates.xml
index e4d4b6d..4f90b38 100644
--- a/updates.xml
+++ b/updates.xml
@@ -1,7 +1,7 @@
--
2.52.0
From 0cc2fea1bc670ccff39b775fdfcd226a0c7dcd9e Mon Sep 17 00:00:00 2001
From: "gitea-actions[bot]"
Date: Thu, 4 Jun 2026 14:50:32 +0000
Subject: [PATCH 05/11] chore: update development channel 02.19.03-dev [skip
ci]
---
updates.xml | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/updates.xml b/updates.xml
index 4f90b38..44b0594 100644
--- a/updates.xml
+++ b/updates.xml
@@ -1,7 +1,7 @@
@@ -11,18 +11,18 @@
mokoonyx
template
site
- 02.19.02-dev
+ 02.19.03-dev
2026-06-04
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development
+ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/tpl_mokoonyx-02.19.02-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/tpl_mokoonyx-02.19.03-dev.zip
- 5811cc175564744865053f5a2cf259e66016c4e41f2d87571d19b1dd5f7fd8ba
+ 08e1a2533860d0e0fd8e8d100d76ff33fcb4dd59627765490d091d17667aa555
dev
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/CHANGELOG.md
Moko Consulting
https://mokoconsulting.tech
-
+
8.1.0
@@ -33,16 +33,16 @@
site
02.20.00
2026-06-04
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable
+ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.20.00.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.20.00.zip
c1a5c28f7a74da65b8f303bd72892e241e2ff5229a2f47dee744b3eb5e7a9163
stable
https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/CHANGELOG.md
Moko Consulting
https://mokoconsulting.tech
-
+
8.1.0
--
2.52.0
From 0e4b0d55cb49355f42ffdd07b27f815dc1d68444 Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Thu, 4 Jun 2026 15:13:25 +0000
Subject: [PATCH 06/11] chore: sync .mokogitea/workflows/pr-check.yml from
moko-platform [skip ci]
---
.mokogitea/workflows/pr-check.yml | 108 ++++++++++++++++++++++++++++++
1 file changed, 108 insertions(+)
diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml
index e2c82ef..9d0cb35 100644
--- a/.mokogitea/workflows/pr-check.yml
+++ b/.mokogitea/workflows/pr-check.yml
@@ -105,6 +105,19 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
+ - name: Check for merge conflict markers
+ run: |
+ CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
+ if [ -n "$CONFLICTS" ]; then
+ echo "::error::Merge conflict markers found in source files"
+ echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ exit 1
+ fi
+ echo "No conflict markers found"
+
- name: Detect platform
id: platform
run: |
@@ -183,6 +196,101 @@ jobs:
;;
esac
+ - name: Validate Joomla language files
+ if: steps.platform.outputs.platform == 'joomla'
+ run: |
+ ERRORS=0
+ WARNINGS=0
+
+ # Find all .ini language files
+ INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null)
+ if [ -z "$INI_FILES" ]; then
+ echo "No .ini language files found — skipping"
+ exit 0
+ fi
+
+ echo "Found $(echo "$INI_FILES" | wc -l) language file(s)"
+
+ for FILE in $INI_FILES; do
+ FNAME=$(basename "$FILE")
+ LINENUM=0
+ SEEN_KEYS=""
+
+ while IFS= read -r line || [ -n "$line" ]; do
+ LINENUM=$((LINENUM + 1))
+
+ # Skip empty lines and comments
+ [ -z "$line" ] && continue
+ echo "$line" | grep -qE '^\s*;' && continue
+ echo "$line" | grep -qE '^\s*$' && continue
+
+ # Must match KEY="VALUE" format
+ if ! echo "$line" | grep -qE '^[A-Z_][A-Z0-9_]*=".*"$'; then
+ echo "::error file=${FILE},line=${LINENUM}::Malformed line: ${line}"
+ ERRORS=$((ERRORS + 1))
+ continue
+ fi
+
+ # Extract key and check for duplicates
+ KEY=$(echo "$line" | sed 's/=.*//')
+ if echo "$SEEN_KEYS" | grep -qx "$KEY"; then
+ echo "::error file=${FILE},line=${LINENUM}::Duplicate key: ${KEY}"
+ ERRORS=$((ERRORS + 1))
+ fi
+ SEEN_KEYS="${SEEN_KEYS}
+ ${KEY}"
+ done < "$FILE"
+
+ echo " ${FILE}: checked ${LINENUM} lines"
+ done
+
+ # Cross-check en-GB vs en-US key consistency
+ GB_DIR=$(find . -path "*/language/en-GB" -type d -not -path "./.git/*" 2>/dev/null | head -1)
+ US_DIR=$(find . -path "*/language/en-US" -type d -not -path "./.git/*" 2>/dev/null | head -1)
+
+ if [ -n "$GB_DIR" ] && [ -n "$US_DIR" ]; then
+ for GB_FILE in "$GB_DIR"/*.ini; do
+ [ ! -f "$GB_FILE" ] && continue
+ FNAME=$(basename "$GB_FILE")
+ US_FILE="$US_DIR/$FNAME"
+ [ ! -f "$US_FILE" ] && continue
+
+ GB_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$GB_FILE" 2>/dev/null | sort)
+ US_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$US_FILE" 2>/dev/null | sort)
+
+ # Keys in en-GB but not en-US
+ MISSING_US=$(comm -23 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
+ if [ -n "$MISSING_US" ]; then
+ echo "::warning::Keys in en-GB/$FNAME but missing from en-US/$FNAME:"
+ echo "$MISSING_US" | while read -r k; do echo " - $k"; done
+ WARNINGS=$((WARNINGS + 1))
+ fi
+
+ # Keys in en-US but not en-GB
+ MISSING_GB=$(comm -13 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
+ if [ -n "$MISSING_GB" ]; then
+ echo "::warning::Keys in en-US/$FNAME but missing from en-GB/$FNAME:"
+ echo "$MISSING_GB" | while read -r k; do echo " - $k"; done
+ WARNINGS=$((WARNINGS + 1))
+ fi
+ done
+ fi
+
+ {
+ echo "### Language File Validation"
+ echo "| Metric | Count |"
+ echo "|---|---|"
+ echo "| Files checked | $(echo "$INI_FILES" | wc -l) |"
+ echo "| Errors | ${ERRORS} |"
+ echo "| Warnings | ${WARNINGS} |"
+ } >> $GITHUB_STEP_SUMMARY
+
+ if [ "$ERRORS" -gt 0 ]; then
+ echo "::error::Language validation failed with ${ERRORS} error(s)"
+ exit 1
+ fi
+ echo "Language files: OK (${WARNINGS} warning(s))"
+
- name: Check changelog has unreleased entry
run: |
if [ ! -f "CHANGELOG.md" ]; then
--
2.52.0
From c6b52100de1f46a344651d73b2b12d37beea2776 Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Thu, 4 Jun 2026 15:28:55 +0000
Subject: [PATCH 07/11] chore: sync .mokogitea/workflows/pr-check.yml from
moko-platform [skip ci]
---
.mokogitea/workflows/pr-check.yml | 39 ++++++++++++++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml
index 9d0cb35..473eeb2 100644
--- a/.mokogitea/workflows/pr-check.yml
+++ b/.mokogitea/workflows/pr-check.yml
@@ -202,10 +202,47 @@ jobs:
ERRORS=0
WARNINGS=0
+ # Require both en-GB and en-US language directories
+ LANG_ROOT=$(find . -path "*/language" -type d -not -path "./.git/*" 2>/dev/null | head -1)
+ if [ -z "$LANG_ROOT" ]; then
+ echo "No language/ directory found — skipping"
+ exit 0
+ fi
+
+ if [ ! -d "$LANG_ROOT/en-GB" ]; then
+ echo "::error::Missing en-GB language directory (${LANG_ROOT}/en-GB)"
+ ERRORS=$((ERRORS + 1))
+ fi
+ if [ ! -d "$LANG_ROOT/en-US" ]; then
+ echo "::error::Missing en-US language directory (${LANG_ROOT}/en-US)"
+ ERRORS=$((ERRORS + 1))
+ fi
+
+ # Check that en-GB and en-US have matching .ini files
+ if [ -d "$LANG_ROOT/en-GB" ] && [ -d "$LANG_ROOT/en-US" ]; then
+ for GB_INI in "$LANG_ROOT/en-GB"/*.ini; do
+ [ ! -f "$GB_INI" ] && continue
+ US_INI="$LANG_ROOT/en-US/$(basename "$GB_INI")"
+ if [ ! -f "$US_INI" ]; then
+ echo "::error::$(basename "$GB_INI") exists in en-GB but missing from en-US"
+ ERRORS=$((ERRORS + 1))
+ fi
+ done
+ for US_INI in "$LANG_ROOT/en-US"/*.ini; do
+ [ ! -f "$US_INI" ] && continue
+ GB_INI="$LANG_ROOT/en-GB/$(basename "$US_INI")"
+ if [ ! -f "$GB_INI" ]; then
+ echo "::error::$(basename "$US_INI") exists in en-US but missing from en-GB"
+ ERRORS=$((ERRORS + 1))
+ fi
+ done
+ fi
+
# Find all .ini language files
INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null)
if [ -z "$INI_FILES" ]; then
- echo "No .ini language files found — skipping"
+ echo "No .ini language files found"
+ [ "$ERRORS" -gt 0 ] && exit 1
exit 0
fi
--
2.52.0
From 6e05b6aea08abd8cece05225c56c07fe7a1e9f9f Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Thu, 4 Jun 2026 15:38:27 +0000
Subject: [PATCH 08/11] chore: sync .mokogitea/workflows/pr-check.yml from
moko-platform [skip ci]
---
.mokogitea/workflows/pr-check.yml | 92 +++++++++++++++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml
index 473eeb2..3dd7540 100644
--- a/.mokogitea/workflows/pr-check.yml
+++ b/.mokogitea/workflows/pr-check.yml
@@ -147,6 +147,98 @@ jobs:
echo "PHP lint: ${ERRORS} error(s)"
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
+ - name: Joomla JEXEC guard check
+ if: steps.platform.outputs.platform == 'joomla'
+ run: |
+ ERRORS=0
+ while IFS= read -r -d '' file; do
+ # Skip vendor, node_modules, and index.html stub files
+ case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
+ # Check first 10 lines for JEXEC or JPATH guard
+ if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
+ echo "::error file=${file}::Missing JEXEC guard: ${file}"
+ ERRORS=$((ERRORS + 1))
+ fi
+ done < <(find . -name "*.php" -path "*/src/*" -not -path "./.git/*" -not -path "./vendor/*" -print0)
+ if [ "$ERRORS" -gt 0 ]; then
+ echo "::error::${ERRORS} PHP file(s) missing defined('_JEXEC') or die guard"
+ echo "## JEXEC Guard Check: Failed" >> $GITHUB_STEP_SUMMARY
+ echo "${ERRORS} file(s) in src/ are missing the Joomla execution guard." >> $GITHUB_STEP_SUMMARY
+ exit 1
+ fi
+ echo "JEXEC guard: OK"
+
+ - name: Joomla directory listing protection
+ if: steps.platform.outputs.platform == 'joomla'
+ run: |
+ MISSING=0
+ SOURCE_DIR="src"
+ [ ! -d "$SOURCE_DIR" ] && exit 0
+ while IFS= read -r dir; do
+ if [ ! -f "${dir}/index.html" ]; then
+ echo "::warning::Missing index.html in ${dir} (directory listing protection)"
+ MISSING=$((MISSING + 1))
+ fi
+ done < <(find "$SOURCE_DIR" -type d -not -path "./.git/*" -not -path "*/vendor/*" -not -path "*/node_modules/*")
+ if [ "$MISSING" -gt 0 ]; then
+ echo "## Directory Protection" >> $GITHUB_STEP_SUMMARY
+ echo "${MISSING} director(ies) missing index.html" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "Directory protection: ${MISSING} missing (advisory)"
+
+ - name: Joomla script file and asset checks
+ if: steps.platform.outputs.platform == 'joomla'
+ run: |
+ ERRORS=0
+ MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1)
+ [ -z "$MANIFEST" ] && exit 0
+ MANIFEST_DIR=$(dirname "$MANIFEST")
+
+ # Check scriptfile exists if declared
+ SCRIPTFILE=$(sed -n 's/.*\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null)
+ if [ -n "$SCRIPTFILE" ]; then
+ if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then
+ echo "::error::Manifest declares ${SCRIPTFILE} but file not found at ${MANIFEST_DIR}/${SCRIPTFILE}"
+ ERRORS=$((ERRORS + 1))
+ else
+ echo "Script file: ${MANIFEST_DIR}/${SCRIPTFILE} (OK)"
+ fi
+ fi
+
+ # Require joomla.asset.json and validate it
+ ASSET_JSON=$(find "$MANIFEST_DIR" -name "joomla.asset.json" -not -path "./.git/*" 2>/dev/null | head -1)
+ if [ -z "$ASSET_JSON" ]; then
+ echo "::error::joomla.asset.json not found — Joomla asset system is required"
+ ERRORS=$((ERRORS + 1))
+ else
+ if command -v php &> /dev/null; then
+ php -r "json_decode(file_get_contents('$ASSET_JSON')); if(json_last_error()!==JSON_ERROR_NONE){echo json_last_error_msg();exit(1);}" 2>&1 || {
+ echo "::error::joomla.asset.json is not valid JSON"
+ ERRORS=$((ERRORS + 1))
+ }
+ fi
+ echo "joomla.asset.json: valid"
+ fi
+
+ # Validate all XML files in src/ are well-formed
+ XML_ERRORS=0
+ if command -v php &> /dev/null; then
+ while IFS= read -r -d '' xmlfile; do
+ if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$xmlfile'); if(!\$x){foreach(libxml_get_errors() as \$e) echo trim(\$e->message) . ' in $xmlfile'; exit(1);}" 2>&1; then
+ XML_ERRORS=$((XML_ERRORS + 1))
+ fi
+ done < <(find "$MANIFEST_DIR" -name "*.xml" -not -path "./.git/*" -print0)
+ fi
+ if [ "$XML_ERRORS" -gt 0 ]; then
+ echo "::error::${XML_ERRORS} XML file(s) are malformed"
+ ERRORS=$((ERRORS + 1))
+ else
+ echo "XML well-formedness: OK"
+ fi
+
+ [ "$ERRORS" -gt 0 ] && exit 1
+ echo "Joomla asset checks: OK"
+
- name: Validate platform manifest
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
--
2.52.0
From 9e6cebb9aaf637652cc414624b703578836f637d Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Thu, 4 Jun 2026 15:42:19 +0000
Subject: [PATCH 09/11] chore: remove updates.xml [skip ci]
---
updates.xml | 48 ------------------------------------------------
1 file changed, 48 deletions(-)
delete mode 100644 updates.xml
diff --git a/updates.xml b/updates.xml
deleted file mode 100644
index 44b0594..0000000
--- a/updates.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
- Template - MokoOnyx
- Template - MokoOnyx development build.
- mokoonyx
- template
- site
- 02.19.03-dev
- 2026-06-04
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/tpl_mokoonyx-02.19.03-dev.zip
-
- 08e1a2533860d0e0fd8e8d100d76ff33fcb4dd59627765490d091d17667aa555
- dev
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/CHANGELOG.md
- Moko Consulting
- https://mokoconsulting.tech
-
- 8.1.0
-
-
- Template - MokoOnyx
- Template - MokoOnyx stable build.
- mokoonyx
- template
- site
- 02.20.00
- 2026-06-04
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.20.00.zip
-
- c1a5c28f7a74da65b8f303bd72892e241e2ff5229a2f47dee744b3eb5e7a9163
- stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/CHANGELOG.md
- Moko Consulting
- https://mokoconsulting.tech
-
- 8.1.0
-
-
--
2.52.0
From 8428f290581785b5234b842af7b319e56a0254a2 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Thu, 4 Jun 2026 10:47:16 -0500
Subject: [PATCH 10/11] chore: update changelog with unreleased entries [skip
bump]
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context)
---
CHANGELOG.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2600f8b..9409eff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,17 @@
# Changelog — MokoOnyx (VERSION: 02.19.03)
## [Unreleased]
+### Fixed
+- Strip Joomla-injected `p-2` padding class from Font Awesome icons in all menu overrides (default, mainmenu, horizontal)
+
+### Changed
+- Migrated update server URL from raw file endpoint to Gitea Pages
+- Release workflow no longer manages updates.xml (decoupled to Gitea Pages)
+- Added conflict-marker guard to PR check and release workflows
+- Added Joomla language file validation (syntax, duplicates, en-GB/en-US consistency)
+- Added JEXEC guard, joomla.asset.json, XML well-formedness, and script file CI checks
+- Removed RS_FTP_PATH_SUFFIX from repo health requirements
+
## [02.19.00] --- 2026-06-04
## [02.18.00] --- 2026-06-02
--
2.52.0
From 041111b8abc10d34fe5ccb2498fa053ada87b785 Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Thu, 4 Jun 2026 15:50:47 +0000
Subject: [PATCH 11/11] chore: standardize updateservers URL [skip ci]
---
src/templateDetails.xml | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/templateDetails.xml b/src/templateDetails.xml
index bcd11a5..d192160 100644
--- a/src/templateDetails.xml
+++ b/src/templateDetails.xml
@@ -31,9 +31,7 @@
-->
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/updates.xml
-
+ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/updates.xml
mokoonyx
02.19.03-dev
--
2.52.0