132 Commits

Author SHA1 Message Date
gitea-actions[bot] d2b4b70f0e chore: promote changelog [Unreleased] → [02.50.00] 2026-06-28 18:29:56 +00:00
gitea-actions[bot] 1c0fa55955 chore(release): build 02.50.00 [skip ci] 2026-06-28 18:29:46 +00:00
jmiller 9eb7ce03e6 Merge pull request 'Release: dev to main - UI cleanup, RL import, menu icons' (#269) from merge/dev-to-main-268 into main 2026-06-28 18:29:17 +00:00
jmiller 7933909ac4 Merge remote-tracking branch 'origin/main' into merge/dev-to-main-268
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Generic: Repo Health / Site Health (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 15s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 30s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 52s
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 59s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 29s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Successful in 5m42s
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
# Conflicts:
#	.mokogitea/workflows/deploy-manual.yml
#	.mokogitea/workflows/issue-branch.yml
2026-06-28 13:27:56 -05:00
gitea-actions[bot] 33998a1685 chore(version): pre-release bump to 02.48.52-dev [skip ci] 2026-06-28 18:17:00 +00:00
gitea-actions[bot] c2d1a8a0e8 chore(version): auto-bump patch 02.48.51-dev [skip ci] 2026-06-28 18:16:48 +00:00
jmiller 68dd129c0f fix: XSS escaping in menu, SPDX header, orphaned docblock, getDbo()
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 14s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 16s
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 13s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 35s
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- htmlspecialchars() on all icon/title output in menu module
- SPDX license header on cache Dispatcher
- Moved orphaned requestNew() docblock to correct location
- Replaced deprecated Factory::getDbo() with DI container pattern

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-28 13:16:30 -05:00
gitea-actions[bot] 5584e09ecd chore(version): pre-release bump to 02.48.50-dev [skip ci] 2026-06-28 13:02:37 +00:00
gitea-actions[bot] 931d93e921 chore(version): pre-release bump to 02.48.49-dev [skip ci] 2026-06-28 00:34:04 +00:00
gitea-actions[bot] dbcc02e1a4 chore(version): pre-release bump to 02.48.48-dev [skip ci] 2026-06-28 00:33:38 +00:00
gitea-actions[bot] efeb996703 chore(version): auto-bump patch 02.48.47-dev [skip ci] 2026-06-28 00:33:25 +00:00
jmiller 1002c55147 fix: unique icons for every MokoSuite component in admin menu
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 21s
Community=comments, HRM=id-badge, OpenGraph=globe, MRP=cogs.
No two components share the same icon now.

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-27 19:32:47 -05:00
gitea-actions[bot] d7f2baeb3e chore(version): pre-release bump to 02.48.46-dev [skip ci] 2026-06-28 00:13:44 +00:00
gitea-actions[bot] 4d4a75cc52 chore(version): pre-release bump to 02.48.45-dev [skip ci] 2026-06-28 00:13:27 +00:00
gitea-actions[bot] ce26dab8fd chore(version): auto-bump patch 02.48.44-dev [skip ci] 2026-06-28 00:13:14 +00:00
jmiller d65d8faf65 feat: single MokoSuite menu item with collapsible ecosystem children
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s
Consolidates all Moko components under one top-level "MokoSuite"
sidebar entry. Each component with subviews is a nested collapsible.
Also: Help link keeps target=_blank but hides external-link icon.

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-27 19:12:59 -05:00
gitea-actions[bot] 6332405853 chore(version): pre-release bump to 02.48.43-dev [skip ci] 2026-06-27 20:46:26 +00:00
gitea-actions[bot] 47f15e4dbb chore(version): auto-bump patch 02.48.42-dev [skip ci] 2026-06-27 20:46:07 +00:00
jmiller a232f2d3b7 fix: use ASCII-safe description in package manifest
Universal: Auto Version Bump / Version Bump (push) Successful in 15s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 25s
Replace em dash with colon to prevent encoding corruption in builds.

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-27 15:45:50 -05:00
gitea-actions[bot] e6fdda02da chore(version): pre-release bump to 02.48.41-dev [skip ci] 2026-06-27 20:45:12 +00:00
gitea-actions[bot] 987e4e4662 chore(version): pre-release bump to 02.48.40-dev [skip ci] 2026-06-27 20:44:43 +00:00
gitea-actions[bot] 173c20164a chore(version): auto-bump patch 02.48.39-dev [skip ci] 2026-06-27 20:44:27 +00:00
jmiller 2a2240b2be fix: remove target=_blank from Help menu redirect
Universal: Auto Version Bump / Version Bump (push) Successful in 17s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 25s
Atum shows an external-link icon for _blank links, disrupting the
sidebar flow. The help link now opens in the same window.

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-27 15:44:07 -05:00
jmiller 64482e59cd chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-06-27 20:43:22 +00:00
gitea-actions[bot] f7cd0851c8 chore(version): pre-release bump to 02.48.38-dev [skip ci] 2026-06-27 20:33:59 +00:00
gitea-actions[bot] ea84e53d48 chore(version): pre-release bump to 02.48.37-dev [skip ci] 2026-06-27 20:33:30 +00:00
gitea-actions[bot] 3196cae2e5 chore(version): auto-bump patch 02.48.36-dev [skip ci] 2026-06-27 20:33:12 +00:00
jmiller 009bc3a8be feat: add icon overrides for all Moko components in admin menu
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 29s
Parent icons from catalog.xml, child icons auto-guessed from view
name (dashboard, contacts, orders, etc.) with fallback to angle-right.

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-27 15:32:35 -05:00
gitea-actions[bot] cae2831fb1 chore(version): pre-release bump to 02.48.35-dev [skip ci] 2026-06-27 20:03:37 +00:00
gitea-actions[bot] cab1cd7ed8 chore(version): pre-release bump to 02.48.34-dev [skip ci] 2026-06-27 20:03:19 +00:00
gitea-actions[bot] 1f73f70fc2 chore(version): auto-bump patch 02.48.33-dev [skip ci] 2026-06-27 20:03:04 +00:00
jmiller 25baf6afd6 feat: merge Cache and Temp buttons into single Clean dropdown
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 22s
Reduces header bar footprint — Site and PIN buttons stay visible,
Cache and Temp are under a Clean ▾ dropdown menu.

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-27 15:02:46 -05:00
gitea-actions[bot] 9148548c62 chore(version): pre-release bump to 02.48.32-dev [skip ci] 2026-06-27 19:51:16 +00:00
gitea-actions[bot] 4731ef6100 chore(version): pre-release bump to 02.48.31-dev [skip ci] 2026-06-27 19:50:59 +00:00
gitea-actions[bot] 32fa117569 chore(version): auto-bump patch 02.48.30-dev [skip ci] 2026-06-27 19:50:49 +00:00
jmiller a8b9f7d165 feat: cpanel module slim bar with collapsible detail panel
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 17s
Replaces full-width card with a compact bar showing site name,
version, status badges, PIN, and IP. Click chevron to expand
the detail panel with environment, stats, disk, and plugin info.

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-27 14:50:32 -05:00
gitea-actions[bot] 0288af9421 chore(version): pre-release bump to 02.48.29-dev [skip ci] 2026-06-27 19:37:24 +00:00
jmiller 4d6a12c378 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-06-27 05:31:46 +00:00
gitea-actions[bot] 5a16c563f6 chore(version): pre-release bump to 02.48.28-dev [skip ci] 2026-06-27 02:20:21 +00:00
gitea-actions[bot] 92016a91e5 chore(version): auto-bump patch 02.48.27-dev [skip ci] 2026-06-27 02:20:11 +00:00
jmiller 94a0bbb160 fix: remove duplicate <install> block from component manifest
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
The second <install> block referenced admin/sql/install.mysql.sql
which resolved to a non-existent path during Joomla package updates,
causing "Install path does not exist" error.

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-26 21:20:00 -05:00
jmiller 467406f2e2 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-27 00:48:53 +00:00
gitea-actions[bot] 3673ca0525 chore(version): pre-release bump to 02.48.26-dev [skip ci] 2026-06-27 00:34:55 +00:00
gitea-actions[bot] 6b6c963bb7 chore(version): auto-bump patch 02.48.25-dev [skip ci] 2026-06-27 00:34:45 +00:00
jmiller 13ce8c6eeb fix: add missing RL destination tables to SQL updates, fix import detection
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
Sites that upgraded never got mokosuiteclient_conditions, _snippets,
_replacements, _content_templates tables — only fresh installs did.
Import banner now requires both source AND destination tables.

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
2026-06-26 19:34:33 -05:00
gitea-actions[bot] bf9c94bbf8 chore(version): pre-release bump to 02.48.24-dev [skip ci] 2026-06-25 19:45:58 +00:00
jmiller 9df6bea4b7 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-25 19:45:36 +00:00
jmiller ef77ae83da chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-25 19:45:34 +00:00
jmiller 1fd90366d6 chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-06-25 19:45:30 +00:00
gitea-actions[bot] 19d4b63ca6 chore(version): pre-release bump to 02.48.23-dev [skip ci] 2026-06-25 17:47:17 +00:00
gitea-actions[bot] 95e0ff04a4 chore(version): pre-release bump to 02.48.22-dev [skip ci] 2026-06-25 17:47:03 +00:00
gitea-actions[bot] 57748a2a23 chore(version): auto-bump patch 02.48.21-dev [skip ci] 2026-06-25 17:46:55 +00:00
jmiller 3f4f2f2d43 feat: auto-enrich IPs with country flags across all admin pages
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
DB-IP plugin onAfterRender scans admin HTML for IP addresses in
<code> tags, looks up geolocation, prepends country flag emoji and
adds city/region/country hover tooltip. Caches lookups per request,
skips private/loopback IPs and already-enriched elements.
2026-06-25 12:46:42 -05:00
gitea-actions[bot] 7ac776ff28 chore(version): pre-release bump to 02.48.20-dev [skip ci] 2026-06-25 17:43:34 +00:00
gitea-actions[bot] 89b71a5a65 chore(version): pre-release bump to 02.48.19-dev [skip ci] 2026-06-25 17:39:16 +00:00
gitea-actions[bot] 0b3eb7c7aa chore(version): pre-release bump to 02.48.18-dev [skip ci] 2026-06-25 17:38:56 +00:00
gitea-actions[bot] 4b809778f4 chore(version): auto-bump patch 02.48.17-dev [skip ci] 2026-06-25 17:38:43 +00:00
jmiller 7ab1fe4cdd fix: PIN copy revert timeout changed to 5 seconds
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 22s
2026-06-25 12:38:25 -05:00
jmiller 814b6879da chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-06-25 17:09:51 +00:00
jmiller 9d67a045f3 chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-25 17:09:51 +00:00
jmiller f4f47ad43a chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-25 17:09:50 +00:00
jmiller c096172c2c chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-25 17:09:49 +00:00
jmiller 35dac96d7a chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-25 17:09:49 +00:00
jmiller fc61577422 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-25 17:09:48 +00:00
jmiller edda6b7a51 chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-25 17:09:47 +00:00
jmiller c9df811602 chore: sync cleanup.yml from Template-Generic [skip ci] 2026-06-25 17:09:47 +00:00
jmiller ff21765973 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-25 17:09:46 +00:00
jmiller 81d2f1ceea chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-06-25 17:09:45 +00:00
jmiller d772f10b6a chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-24 11:49:57 +00:00
jmiller 91edc73336 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-23 23:07:38 +00:00
jmiller 7f1bcb23bf chore: remove security-audit.yml -- handled by MokoGitea
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 31s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
2026-06-23 18:27:15 +00:00
jmiller bdb135bcb4 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-23 17:50:34 +00:00
jmiller 1781ee8c67 chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-23 17:50:33 +00:00
jmiller 803464a584 chore: remove deprecated .mokogitea/workflows/composer-publish.yml [skip ci] 2026-06-23 17:36:40 +00:00
jmiller 345483c6f9 chore: remove deprecated .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-06-23 17:36:37 +00:00
Jonathan Miller 19bbe92780 Merge branch 'dev' into main — v02.47 unified UI, tours, cache module
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 41s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Resolve issue-branch.yml version conflict (take dev).
2026-06-23 12:26:10 -05:00
jmiller 3aa1b43e96 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-23 16:04:29 +00:00
Jonathan Miller 27d26f15ca Merge branch 'dev' into main for v02.47 release
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 1m7s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Resolves version/date conflicts by taking dev branch changes.
Removed monitor plugin and ticket automation (retired in dev).
2026-06-23 10:59:31 -05:00
jmiller a8d1e8f276 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-22 00:34:31 +00:00
jmiller f143a959be chore: remove unused Makefile - builds handled by CI auto-release
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 48s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
2026-06-21 23:55:15 +00:00
gitea-actions[bot] 5e889bbcff chore: promote changelog [Unreleased] → [02.46.99] 2026-06-21 23:21:59 +00:00
gitea-actions[bot] c46373265d chore(release): build 02.46.99 [skip ci]
Publish to Composer / Publish Package (release) Failing after 43s
2026-06-21 23:21:54 +00:00
jmiller 8044106f19 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-21 23:20:45 +00:00
jmiller 291f04eb81 Merge pull request 'chore: remove automation directory' (#234) from fix/remove-automation into main
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 1m0s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
2026-06-21 23:09:57 +00:00
gitea-actions[bot] dca4ef89a9 chore(version): pre-release bump to 02.46.99-dev [skip ci]
Publish to Composer / Publish Package (release) Failing after 41s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 26s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 14m26s
2026-06-21 23:08:23 +00:00
Jonathan Miller ffa9edd33f chore: remove automation directory
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 8s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 12s
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: PR Check / Secret Scan (pull_request) Successful in 12s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 13s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 1m17s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 27s
Universal: Build & Release / Promote to RC (pull_request) Failing after 13s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-21 18:02:48 -05:00
jmiller 70fe78e064 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-21 22:02:18 +00:00
jmiller ceb3cfacf7 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-21 16:05:05 +00:00
jmiller 6ecaf9923d chore: sync composer-publish.yml from Template-Generic [skip ci] 2026-06-21 06:34:27 +00:00
jmiller e8e8c689e8 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-06-21 01:27:18 +00:00
jmiller d26ada7d18 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-21 01:27:14 +00:00
jmiller f6e7082f44 ci: sync rc-revert.yml from Template-Joomla [skip ci] 2026-06-21 00:15:04 +00:00
jmiller 5c048ef5db ci: sync issue-branch.yml from Template-Joomla [skip ci] 2026-06-21 00:14:35 +00:00
jmiller 10b597b248 ci: sync ci-joomla.yml from Template-Joomla [skip ci] 2026-06-21 00:14:11 +00:00
jmiller 7f272aabf9 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-20 23:45:48 +00:00
jmiller 44030cdc9c chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-06-20 23:45:48 +00:00
jmiller e21e345389 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-06-20 23:45:47 +00:00
jmiller 43646e826d chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-20 23:07:34 +00:00
gitea-actions[bot] 91542cf759 chore: promote changelog [Unreleased] → [02.45.00] 2026-06-20 23:07:33 +00:00
gitea-actions[bot] c4a77e2da7 chore(release): build 02.45.00 [skip ci] 2026-06-20 23:07:26 +00:00
jmiller 156cb1713f Merge pull request 'fix(heartbeat): align signature headers with HQ expectations' (#222) from rc into main
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 28s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
2026-06-20 23:07:02 +00:00
jmiller 59f37f09cf chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-20 22:29:48 +00:00
jmiller 1308497e39 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-06-20 22:29:48 +00:00
jmiller 481893e182 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-20 22:29:47 +00:00
jmiller 8606acf2fd chore: sync cleanup.yml from Template-Generic [skip ci] 2026-06-20 22:29:46 +00:00
jmiller 71a9da3f72 ci: sync security-audit.yml from Template-Joomla [skip ci] 2026-06-20 22:26:30 +00:00
jmiller c42b65ed38 ci: sync repo-health.yml from Template-Joomla [skip ci] 2026-06-20 22:26:01 +00:00
jmiller 9949bf7fda ci: sync rc-revert.yml from Template-Joomla [skip ci] 2026-06-20 22:25:52 +00:00
jmiller 44ca197c36 ci: sync pr-check.yml from Template-Joomla [skip ci] 2026-06-20 22:24:45 +00:00
jmiller 706c088da1 ci: sync issue-branch.yml from Template-Joomla [skip ci] 2026-06-20 22:22:20 +00:00
jmiller 1ebba18c16 ci: sync cleanup.yml from Template-Joomla [skip ci] 2026-06-20 22:15:33 +00:00
jmiller 70c2aaae05 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-20 21:35:12 +00:00
jmiller 9085ccf474 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-06-20 21:35:11 +00:00
jmiller 9d22ba0b10 ci: sync ci-generic.yml from Template-Joomla [skip ci] 2026-06-20 21:34:01 +00:00
jmiller f03a522bb9 ci: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-06-20 21:31:32 +00:00
jmiller 344673ab8a ci: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-06-20 21:28:07 +00:00
jmiller 43b8549402 ci: sync auto-release.yml from Template-Joomla [skip ci] 2026-06-20 21:26:56 +00:00
gitea-actions[bot] a4f55f6ba7 chore: promote changelog [Unreleased] → [02.44.00] 2026-06-20 20:56:18 +00:00
gitea-actions[bot] 222a52580c chore(release): build 02.44.00 [skip ci] 2026-06-20 20:56:10 +00:00
jmiller 1c6c8a8473 Merge pull request 'fix(heartbeat): correct API route from mokosuiteclienthq to mokosuitehq' (#221) from rc into main
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 48s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
2026-06-20 20:52:36 +00:00
jmiller 1964c86ee0 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-06-20 20:50:43 +00:00
jmiller cd7bdc03c8 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-20 20:50:41 +00:00
gitea-actions[bot] 9d8fd4eed1 chore: promote changelog [Unreleased] → [02.43.00] 2026-06-20 20:41:04 +00:00
gitea-actions[bot] 1404b699ad chore(release): build 02.43.00 [skip ci] 2026-06-20 20:40:58 +00:00
jmiller b1d72bc23e ci: sync ci-generic.yml from Template-Joomla [skip ci] 2026-06-20 20:35:04 +00:00
gitea-actions[bot] 67721d0247 chore: promote changelog [Unreleased] → [02.42.00] 2026-06-20 20:33:33 +00:00
gitea-actions[bot] 7687da58c3 chore(release): build 02.42.00 [skip ci] 2026-06-20 20:33:23 +00:00
jmiller a4d9d6d129 Merge pull request 'fix: update heartbeat URL from waas.dev to suite.dev.mokoconsulting.tech' (#220) from rc into main
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 46s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
2026-06-20 20:33:01 +00:00
jmiller 495083f89f ci: sync cascade-dev.yml from Template-Joomla [skip ci] 2026-06-20 20:32:50 +00:00
gitea-actions[bot] f47554e46c chore(version): auto-bump patch 02.41.02-rc [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 28s
2026-06-20 20:32:26 +00:00
jmiller eddc9c2fd4 ci: sync branch-cleanup.yml from Template-Joomla [skip ci] 2026-06-20 20:31:52 +00:00
jmiller b88e68ee10 ci: sync auto-release.yml from Template-Joomla [skip ci] 2026-06-20 20:30:58 +00:00
66 changed files with 2722 additions and 2130 deletions
+66 -66
View File
@@ -1,66 +1,66 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
name: "Universal: Auto Version Bump"
on:
push:
branches:
- dev
- rc
- 'feature/**'
- 'patch/**'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
permissions:
contents: write
jobs:
bump:
name: Version Bump
runs-on: release
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip bump]') &&
!startsWith(github.event.head_commit.message, 'Merge pull request')
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup mokocli tools
run: |
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
if [ -d "/opt/mokocli/cli" ]; then
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
fi
- name: Bump version
run: |
php ${MOKO_CLI}/version_auto_bump.php \
--path . --branch "${GITHUB_REF_NAME}" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
name: "Universal: Auto Version Bump"
on:
push:
branches:
- dev
- rc
- 'feature/**'
- 'patch/**'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
permissions:
contents: write
jobs:
bump:
name: Version Bump
runs-on: release
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip bump]') &&
!startsWith(github.event.head_commit.message, 'Merge pull request')
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup mokocli tools
run: |
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
if [ -d "/opt/mokocli/cli" ]; then
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
fi
- name: Bump version
run: |
php ${MOKO_CLI}/version_auto_bump.php \
--path . --branch "${GITHUB_REF_NAME}" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
+79 -15
View File
@@ -10,9 +10,9 @@
# VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml
#
# +========================================================================+
# +=======================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+
# +=======================================================================+
# | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | |
@@ -21,15 +21,24 @@
# | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream |
# | |
# +========================================================================+
# +=======================================================================+
name: "Universal: Build & Release"
on:
pull_request:
types: [opened, closed]
types: [opened, synchronize, closed]
branches:
- main
paths-ignore:
- '.mokogitea/workflows/**'
- '*.md'
- 'wiki/**'
- '.editorconfig'
- '.gitignore'
- '.gitattributes'
- '.gitmessage'
- 'LICENSE'
workflow_dispatch:
inputs:
action:
@@ -43,7 +52,7 @@ on:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
@@ -51,12 +60,13 @@ permissions:
contents: write
jobs:
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
promote-rc:
name: Promote to RC
runs-on: release
if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps:
@@ -92,7 +102,7 @@ jobs:
php ${MOKO_CLI}/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--api-base "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git
@@ -111,7 +121,7 @@ jobs:
- name: Update RC release notes from CHANGELOG.md
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Extract [Unreleased] section from changelog
@@ -149,7 +159,7 @@ jobs:
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
# ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
release:
name: Build & Release Pipeline
runs-on: release
@@ -205,6 +215,12 @@ jobs:
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: "Detect platform"
id: platform
run: |
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true
- name: "Determine version bump level"
id: bump
run: |
@@ -228,9 +244,57 @@ jobs:
--path . --stability stable ${BUMP_FLAG} --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: "Read published version"
id: version
run: |
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [[ "$PLATFORM" == joomla* ]]; then
echo "tag=stable" >> "$GITHUB_OUTPUT"
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
else
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
fi
echo "branch=main" >> "$GITHUB_OUTPUT"
echo "Published version: ${VERSION}"
- name: "Create semver tag for non-Joomla repos"
id: semver
if: |
steps.version.outputs.skip != 'true' &&
!startsWith(steps.platform.outputs.platform, 'joomla')
run: |
VERSION="${{ steps.version.outputs.version }}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
SEMVER_TAG="v${VERSION}"
echo "Creating semver tag: ${SEMVER_TAG}"
# Create the git tag via API
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/tags" \
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "Created semver tag: ${SEMVER_TAG}"
elif [ "$HTTP_CODE" = "409" ]; then
echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
else
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
fi
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
- name: Update release notes and promote changelog
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Get the stable release info (version and ID)
@@ -299,7 +363,7 @@ jobs:
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
@@ -328,7 +392,7 @@ jobs:
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc)
@@ -352,7 +416,7 @@ jobs:
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}"
@@ -373,7 +437,7 @@ jobs:
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
@@ -399,5 +463,5 @@ jobs:
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](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
@@ -0,0 +1,68 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
name: "Universal: CI Issue Reporter"
on:
workflow_call:
inputs:
gate:
description: "CI gate name (e.g. PR Validation, Repository Health)"
required: true
type: string
details:
description: "Human-readable failure description"
required: true
type: string
severity:
description: "error or warning"
required: false
type: string
default: "error"
workflow:
description: "Workflow name for the issue title"
required: false
type: string
default: ""
secrets:
MOKOGITEA_TOKEN:
required: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
report:
name: "Report: ${{ inputs.gate }}"
runs-on: ubuntu-latest
steps:
- name: Clone MokoCLI
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
- name: Report CI failure
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
/tmp/mokocli/cli/ci_issue_reporter.sh \
--gate "${{ inputs.gate }}" \
--details "${{ inputs.details }}" \
--severity "${{ inputs.severity }}" \
--workflow "${{ inputs.workflow }}"
+410 -7
View File
@@ -45,17 +45,17 @@ jobs:
fi
php -v && composer --version
- name: Setup moko-platform tools
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.GA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
run: |
if [ -d "/tmp/moko-platform" ] || [ -d "/opt/moko-platform" ]; then
echo "moko-platform already available on runner — skipping clone"
if [ -d "/opt/mokocli" ] || [ -d "/tmp/mokocli" ]; then
echo "mokocli already available on runner — skipping clone"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform 2>/dev/null || echo "moko-platform clone skipped — continuing without it"
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git" \
/tmp/mokocli 2>/dev/null || echo "mokocli clone skipped — continuing without it"
fi
- name: Install dependencies
@@ -245,10 +245,413 @@ jobs:
echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY
fi
- name: Check config.xml and access.xml for components
run: |
echo "### Component Config & ACL Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find all component manifests (XML with type="component")
COMP_MANIFESTS=$(find . -maxdepth 4 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -exec grep -l '<extension[^>]*type="component"' {} ; 2>/dev/null || true)
if [ -z "$COMP_MANIFESTS" ]; then
echo "No component extensions found — skipping." >> $GITHUB_STEP_SUMMARY
else
for MANIFEST in $COMP_MANIFESTS; do
COMP_DIR=$(dirname "$MANIFEST")
COMP_NAME=$(basename "$COMP_DIR")
echo "Component: `${COMP_NAME}` (manifest: `${MANIFEST}`)" >> $GITHUB_STEP_SUMMARY
# Check access.xml exists
ACCESS_FILE=$(find "$COMP_DIR" -name "access.xml" -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$ACCESS_FILE" ]; then
echo "- Missing `access.xml` — ACL permissions will not work." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
if command -v php &> /dev/null; then
if ! php -r "@simplexml_load_file('$ACCESS_FILE') ?: exit(1);" 2>/dev/null; then
echo "- `access.xml` is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
for ACTION in core.admin core.manage; do
if ! grep -q "name=\"${ACTION}\"" "$ACCESS_FILE" 2>/dev/null; then
echo "- `access.xml` missing required action: `${ACTION}`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
echo "- `access.xml`: valid" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
# Check config.xml exists
CONFIG_FILE=$(find "$COMP_DIR" -name "config.xml" -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$CONFIG_FILE" ]; then
echo "- Missing `config.xml` — component Options page will be empty." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
if command -v php &> /dev/null; then
if ! php -r "@simplexml_load_file('$CONFIG_FILE') ?: exit(1);" 2>/dev/null; then
echo "- `config.xml` is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "- `config.xml`: valid" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} config/ACL issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Component config & ACL check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: SQL schema validation
run: |
echo "### SQL Schema Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find SQL files in source/htdocs
SQL_FILES=$(find . -name "*.sql" -path "*/sql/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$SQL_FILES" ]; then
echo "No SQL files found — skipping." >> $GITHUB_STEP_SUMMARY
else
echo "Found $(echo "$SQL_FILES" | wc -l) SQL file(s)" >> $GITHUB_STEP_SUMMARY
for FILE in $SQL_FILES; do
# Basic syntax check: balanced parentheses, no empty files
SIZE=$(wc -c < "$FILE" | tr -d ' ')
if [ "$SIZE" -eq 0 ]; then
echo "- Empty SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
continue
fi
# Check for common SQL errors
if grep -qP '^\s*$' "$FILE" && [ "$SIZE" -lt 5 ]; then
echo "- Whitespace-only SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
continue
fi
echo "- \`${FILE}\`: ${SIZE} bytes" >> $GITHUB_STEP_SUMMARY
done
# Check update SQL files follow version numbering pattern
UPDATE_DIR=$(find . -path "*/sql/updates/mysql" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -n "$UPDATE_DIR" ]; then
BAD_NAMES=0
for UFILE in "$UPDATE_DIR"/*.sql; do
[ ! -f "$UFILE" ] && continue
BASENAME=$(basename "$UFILE" .sql)
if ! echo "$BASENAME" | grep -qP '^\d+\.\d+\.\d+'; then
echo "- Update file \`${UFILE}\` does not follow version naming (expected X.Y.Z.sql)" >> $GITHUB_STEP_SUMMARY
BAD_NAMES=$((BAD_NAMES + 1))
fi
done
if [ "$BAD_NAMES" -gt 0 ]; then
ERRORS=$((ERRORS + BAD_NAMES))
fi
fi
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} SQL issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**SQL schema validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Manifest file references check
run: |
echo "### Manifest File References" >> $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 [ -z "$MANIFEST" ]; then
echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY
else
MANIFEST_DIR=$(dirname "$MANIFEST")
# Check <filename> references
FILENAMES=$(grep -oP '<filename[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
for F in $FILENAMES; do
if [ ! -f "${MANIFEST_DIR}/${F}" ] && [ ! -d "${MANIFEST_DIR}/${F}" ]; then
echo "- Missing: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
# Check <folder> references
FOLDERS=$(grep -oP '<folder[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
for F in $FOLDERS; do
if [ ! -d "${MANIFEST_DIR}/${F}" ]; then
echo "- Missing folder: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
# Check <file> references in package manifests (ZIP files won't exist in source)
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ "$EXT_TYPE" != "package" ]; then
FILES=$(grep -oP '<file[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
for F in $FILES; do
if [ ! -f "${MANIFEST_DIR}/${F}" ]; then
echo "- Missing file: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
fi
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} missing file reference(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Manifest file references check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Form XML validation
run: |
echo "### Form XML Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
FORM_FILES=$(find . -name "*.xml" -path "*/forms/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$FORM_FILES" ]; then
echo "No form XML files found — skipping." >> $GITHUB_STEP_SUMMARY
else
echo "Found $(echo "$FORM_FILES" | wc -l) form file(s)" >> $GITHUB_STEP_SUMMARY
for FILE in $FORM_FILES; do
if command -v php &> /dev/null; then
if ! php -r "@simplexml_load_file('$FILE') ?: exit(1);" 2>/dev/null; then
echo "- \`${FILE}\`: malformed XML" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
# Check for valid Joomla form structure
if ! grep -qE '<form|<field|<fieldset' "$FILE" 2>/dev/null; then
echo "- \`${FILE}\`: no \`<form>\`, \`<field>\`, or \`<fieldset>\` elements found" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "- \`${FILE}\`: valid" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} form XML issue(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Form XML validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Deprecated Joomla API check
continue-on-error: true
run: |
echo "### Deprecated Joomla API Check" >> $GITHUB_STEP_SUMMARY
WARNINGS=0
SRC_DIR=""
for DIR in source/ src/ htdocs/; do
[ -d "$DIR" ] && SRC_DIR="$DIR" && break
done
if [ -z "$SRC_DIR" ]; then
echo "No source directory found — skipping." >> $GITHUB_STEP_SUMMARY
else
# Joomla 3/4 deprecated patterns that break in Joomla 6
PATTERNS=(
'JFactory::'
'JText::'
'JHtml::'
'JRoute::'
'JUri::'
'JLog::'
'JTable::'
'JInput'
'CMSFactory::\$application'
'JApplicationCms'
)
for PATTERN in "${PATTERNS[@]}"; do
HITS=$(grep -rnl "$PATTERN" "$SRC_DIR" --include="*.php" 2>/dev/null || true)
if [ -n "$HITS" ]; then
COUNT=$(echo "$HITS" | wc -l)
echo "- \`${PATTERN}\` found in ${COUNT} file(s)" >> $GITHUB_STEP_SUMMARY
WARNINGS=$((WARNINGS + COUNT))
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$WARNINGS" -gt 0 ]; then
echo "**${WARNINGS} deprecated API usage(s) found.** These will break in Joomla 6." >> $GITHUB_STEP_SUMMARY
else
echo "**No deprecated APIs found.**" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Template output escaping check
continue-on-error: true
run: |
echo "### Template Output Escaping" >> $GITHUB_STEP_SUMMARY
WARNINGS=0
TMPL_FILES=$(find . -name "*.php" -path "*/tmpl/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$TMPL_FILES" ]; then
echo "No template files found — skipping." >> $GITHUB_STEP_SUMMARY
else
echo "Found $(echo "$TMPL_FILES" | wc -l) template file(s)" >> $GITHUB_STEP_SUMMARY
for FILE in $TMPL_FILES; do
# Check for unescaped output: <?= $var ?> or echo $var without escape()
UNESCAPED=$(grep -nP '<\?=\s*\$(?!this->escape)' "$FILE" 2>/dev/null || true)
if [ -n "$UNESCAPED" ]; then
HITS=$(echo "$UNESCAPED" | wc -l)
echo "- \`${FILE}\`: ${HITS} unescaped \`<?= \$var ?>\` output(s) — use \`<?= \$this->escape(\$var) ?>\`" >> $GITHUB_STEP_SUMMARY
WARNINGS=$((WARNINGS + HITS))
fi
# Check for echo without escaping in template context
RAW_ECHO=$(grep -nP '^\s*echo\s+\$(?!this->escape)' "$FILE" 2>/dev/null || true)
if [ -n "$RAW_ECHO" ]; then
HITS=$(echo "$RAW_ECHO" | wc -l)
echo "- \`${FILE}\`: ${HITS} raw \`echo \$var\` — consider \`echo \$this->escape(\$var)\`" >> $GITHUB_STEP_SUMMARY
WARNINGS=$((WARNINGS + HITS))
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$WARNINGS" -gt 0 ]; then
echo "**${WARNINGS} potential XSS risk(s) in templates.** Review unescaped output." >> $GITHUB_STEP_SUMMARY
else
echo "**All template output appears properly escaped.**" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Namespace consistency check
run: |
echo "### Namespace Consistency" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find component/plugin manifests with <namespace> tags
MANIFESTS=$(find . -maxdepth 4 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -exec grep -l '<namespace' {} \; 2>/dev/null || true)
if [ -z "$MANIFESTS" ]; then
echo "No manifests with \`<namespace>\` found — skipping." >> $GITHUB_STEP_SUMMARY
else
for MANIFEST in $MANIFESTS; do
NS_PATH=$(grep -oP '<namespace[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1)
[ -z "$NS_PATH" ] && continue
MANIFEST_DIR=$(dirname "$MANIFEST")
echo "Manifest: \`${MANIFEST}\` → namespace \`${NS_PATH}\`" >> $GITHUB_STEP_SUMMARY
# Check PHP files have matching namespace
while IFS= read -r -d '' PHP_FILE; do
FILE_NS=$(grep -oP '^\s*namespace\s+\K[^;]+' "$PHP_FILE" 2>/dev/null | head -1)
[ -z "$FILE_NS" ] && continue
# Namespace should start with the manifest namespace path
if ! echo "$FILE_NS" | grep -qF "${NS_PATH}"; then
echo "- \`${PHP_FILE}\`: namespace \`${FILE_NS}\` doesn't match manifest \`${NS_PATH}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done < <(find "$MANIFEST_DIR" -name "*.php" -path "*/src/*" -not -path "./vendor/*" -print0 2>/dev/null)
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} namespace mismatch(es).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Namespace consistency check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: SPDX license header check
continue-on-error: true
run: |
echo "### SPDX License Headers" >> $GITHUB_STEP_SUMMARY
MISSING=0
SRC_DIR=""
for DIR in source/ src/ htdocs/; do
[ -d "$DIR" ] && SRC_DIR="$DIR" && break
done
if [ -z "$SRC_DIR" ]; then
echo "No source directory found — skipping." >> $GITHUB_STEP_SUMMARY
else
TOTAL=0
while IFS= read -r -d '' FILE; do
TOTAL=$((TOTAL + 1))
if ! head -10 "$FILE" | grep -qi "SPDX"; then
echo "- Missing SPDX header: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY
MISSING=$((MISSING + 1))
fi
done < <(find "$SRC_DIR" -name "*.php" -not -path "./vendor/*" -print0)
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$MISSING" -gt 0 ]; then
echo "**${MISSING}/${TOTAL} PHP file(s) missing SPDX license header.**" >> $GITHUB_STEP_SUMMARY
else
echo "**All ${TOTAL} PHP files have SPDX headers.**" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Service provider check
run: |
echo "### Service Provider Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
PROVIDERS=$(find . -name "provider.php" -path "*/services/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$PROVIDERS" ]; then
echo "No service providers found — skipping." >> $GITHUB_STEP_SUMMARY
else
for FILE in $PROVIDERS; do
# Must return a ServiceProviderInterface
if ! grep -qP 'ServiceProviderInterface|ComponentInterface|MVCFactoryInterface|DispatcherInterface' "$FILE" 2>/dev/null; then
echo "- \`${FILE}\`: does not reference ServiceProviderInterface or component interfaces" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "- \`${FILE}\`: valid service provider" >> $GITHUB_STEP_SUMMARY
fi
# Must have return statement
if ! grep -qP '^\s*return\s+new\s+' "$FILE" 2>/dev/null; then
echo "- \`${FILE}\`: missing \`return new ...\` statement" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} service provider issue(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Service provider check passed.**" >> $GITHUB_STEP_SUMMARY
fi
release-readiness:
name: Release Readiness Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.base_ref == 'main'
continue-on-error: true
steps:
- name: Checkout repository
+10 -10
View File
@@ -21,7 +21,7 @@ permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs:
cleanup:
@@ -33,17 +33,17 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GA_TOKEN }}
token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Delete merged branches
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
echo "=== Merged Branch Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0
@@ -56,7 +56,7 @@ jobs:
# Check if branch is merged into main
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
echo " Deleting merged branch: ${BRANCH}"
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/branches/${BRANCH}" 2>/dev/null || true
DELETED=$((DELETED + 1))
fi
@@ -66,20 +66,20 @@ jobs:
- name: Clean old workflow runs
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
echo "=== Workflow Run Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/actions/runs?status=completed&limit=50" | \
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
DELETED=0
for RUN_ID in $RUNS; do
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1))
done
+126
View File
@@ -0,0 +1,126 @@
# 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-API
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.07.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
name: "Universal: 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: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: |
php -v && composer --version
- name: Setup MokoStandards tools
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api 2>/dev/null || true
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Check FTP configuration
id: check
env:
HOST: ${{ vars.DEV_FTP_HOST }}
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
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%/}"
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
-4
View File
@@ -25,10 +25,6 @@
name: "Universal: Secret Scanning"
on:
pull_request:
branches:
- main
- 'dev/**'
schedule:
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
workflow_dispatch:
+6 -6
View File
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# VERSION: 02.48.16
# INGROUP: mokocli.Automation
# VERSION: 02.50.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
@@ -19,7 +19,7 @@ permissions:
issues: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs:
create-branch:
@@ -28,8 +28,8 @@ jobs:
steps:
- name: Create branch and comment
run: |
TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}"
@@ -58,7 +58,7 @@ jobs:
echo "Created branch: ${BRANCH}"
# Comment on issue with branch link
REPO_URL="${GITEA_URL}/${{ github.repository }}"
REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}"
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
curl -sf -X POST \
File diff suppressed because it is too large Load Diff
+22 -1
View File
@@ -7,7 +7,7 @@
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00
# VERSION: 05.02.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release"
@@ -59,6 +59,11 @@ jobs:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.ref_name }}
submodules: recursive
- name: Update submodules to main
run: |
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
- name: Setup mokocli tools
env:
@@ -88,8 +93,20 @@ jobs:
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Check platform eligibility (Joomla only)
id: eligibility
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
echo "proceed=true" >> "$GITHUB_OUTPUT"
else
echo "proceed=false" >> "$GITHUB_OUTPUT"
echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump"
fi
- name: Resolve metadata and bump version
id: meta
if: steps.eligibility.outputs.proceed == 'true'
run: |
# Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "push" ]; then
@@ -166,6 +183,7 @@ jobs:
- name: Create release
id: release
if: steps.eligibility.outputs.proceed == 'true'
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
@@ -176,6 +194,7 @@ jobs:
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md
if: steps.eligibility.outputs.proceed == 'true'
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
@@ -212,6 +231,7 @@ jobs:
- name: Build package and upload
id: package
if: steps.eligibility.outputs.proceed == 'true'
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
@@ -225,6 +245,7 @@ jobs:
# No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)"
if: steps.eligibility.outputs.proceed == 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+20 -15
View File
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoPlatform.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/rc-revert.yml
# VERSION: 09.23.00
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
@@ -29,12 +29,20 @@ jobs:
steps:
- name: Rename branch
env:
BRANCH: ${{ github.event.pull_request.head.ref }}
REPO: ${{ github.repository }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
BRANCH="${{ github.event.pull_request.head.ref }}"
set -euo pipefail
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
fi
SUFFIX="${BRANCH#rc/}"
DEV_BRANCH="dev/${SUFFIX}"
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${REPO}/branches"
# Create dev/ branch from rc/ branch
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
@@ -42,25 +50,22 @@ jobs:
-H "Content-Type: application/json" \
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
"${API}" 2>/dev/null || true)
if [ "$STATUS" = "201" ]; then
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"
exit 1
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
fi
# Delete rc/ branch
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${TOKEN}" \
"${API}/${ENCODED}" 2>/dev/null || true)
if [ "$STATUS" = "204" ]; then
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
fi
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
File diff suppressed because it is too large Load Diff
+130
View File
@@ -0,0 +1,130 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogitea/workflows/version-set.yml
# VERSION: 01.00.00
# BRIEF: Set or reset the extension version across all version-bearing files
name: "Joomla: Set Version"
on:
workflow_dispatch:
inputs:
version:
description: "Version number (e.g. 01.00.00)"
required: true
type: string
branch:
description: "Branch to update (default: current)"
required: false
type: string
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
set-version:
name: Set Version to ${{ inputs.version }}
runs-on: ubuntu-latest
steps:
- name: Validate version format
run: |
VERSION="${{ inputs.version }}"
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
exit 1
fi
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 1
- name: Update manifest version
run: |
MANIFEST=""
for XML_FILE in $(find . -maxdepth 3 -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 "::warning::No Joomla extension manifest found — skipping manifest update"
else
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
fi
- name: Update README.md version
run: |
if [ -f "README.md" ]; then
if grep -qP '^\s*VERSION:\s*\d' README.md; then
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
echo "README.md version updated to ${VERSION}"
else
echo "::warning::No VERSION line found in README.md — skipping"
fi
fi
- name: Update CHANGELOG.md
run: |
if [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
# Check if this version already has an entry
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
else
# Insert new version entry after [Unreleased] or at the top after header
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
else
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
fi
echo "CHANGELOG.md: added entry for ${VERSION}"
fi
else
echo "::warning::No CHANGELOG.md found — skipping"
fi
- name: Update FILE INFORMATION blocks
run: |
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
while IFS= read -r -d '' FILE; do
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
echo "Updated FILE INFORMATION VERSION in ${FILE}"
fi
done
- name: Commit and push
run: |
git config user.name "Moko Consulting [bot]"
git config user.email "hello@mokoconsulting.tech"
git add -A
if git diff --cached --quiet; then
echo "No version changes detected — nothing to commit"
else
git commit -m "chore: set version to ${VERSION} [skip bump]
Authored-by: Moko Consulting"
git push
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
fi
+17 -9
View File
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoPlatform.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
# VERSION: 01.01.00
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
@@ -13,6 +13,7 @@
name: "Universal: Workflow Sync Trigger"
on:
workflow_dispatch:
pull_request:
types: [closed]
branches:
@@ -26,8 +27,9 @@ jobs:
name: Sync workflows to live repos
runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]')
github.event_name == 'workflow_dispatch' ||
(github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]'))
steps:
- name: Determine platform from repo name
@@ -45,16 +47,22 @@ jobs:
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Platform: ${PLATFORM:-all}"
- name: Clone mokoplatform
- name: Clone mokocli
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokoplatform.git" /tmp/mokoplatform
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
- name: Install PHP
run: |
if ! command -v php &> /dev/null; then
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
fi
- name: Install dependencies
run: |
cd /tmp/mokoplatform
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
- name: Run workflow sync
@@ -70,4 +78,4 @@ jobs:
ARGS="${ARGS} --platform-filter ${PLATFORM}"
fi
php /tmp/mokoplatform/cli/workflow_sync.php ${ARGS}
php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
+11 -1
View File
@@ -14,13 +14,18 @@
INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./CHANGELOG.md
VERSION: 02.48.16
VERSION: 02.50.00
BRIEF: Version history using `Keep a Changelog`
-->
# Changelog
## [Unreleased]
## [02.50.00] --- 2026-06-28
## [02.50.00] --- 2026-06-28
### Added
- **Mirror Domains & Staging** — repeatable subform table in DevTools plugin for configuring domain aliases with per-alias offline bypass, robots directive, and labels
- **Daily Support PIN** — HMAC-SHA256 rotating PIN shown on cpanel module, component dashboard, and HQ site cards
@@ -64,6 +69,11 @@
- **Update server migration** — removed migrateUpdateServerUrls, cleanupStaleUpdateSites, fixUpdateRecords, enableUpdateServer calls
### Fixed
- **Regular Labs import** — destination tables missing from SQL update files; sites that upgraded never got the tables, causing "No data found" on import
- **Regular Labs import banner** — detection now requires both source AND destination tables before showing the import button
- **DB-IP auto-enrichment** — all IPs in `<code>` tags in admin backend now show country flag emoji and geo tooltip on hover
- **MokoSuiteBackup quick action** — dashboard now includes MokoSuiteBackup button when component is installed
- **PIN copy** — fixed duplicate click handlers (4 toast messages), "Copied!" not reverting, added "Click to copy" hover tooltip
- Health endpoint cron check SQL error — orphan `setQuery(getQuery(true), 0, 5)` produced bare `LIMIT 5`, returning 503 for all health polls
- License plugin missing `src/` and `language/` directories causing install failure
- PIN generation inconsistency — controller used `floor(now/TTL)` while display used `floor(requestedAt/TTL)`
+1 -1
View File
@@ -14,7 +14,7 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
-->
+1 -1
View File
@@ -19,7 +19,7 @@
DEFGROUP: mokoconsulting-tech.MokoSuiteClientBrand
INGROUP: MokoStandards.Governance
REPO: https://github.com/mokoconsulting-tech/MokoSuiteClientBrand
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /GOVERNANCE.md
BRIEF: Project governance rules, roles, and decision process for MokoSuiteClientBrand
-->
+1 -1
View File
@@ -15,7 +15,7 @@
INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: ./LICENSE.md
VERSION: 02.48.16
VERSION: 02.50.00
BRIEF: Project license (GPL-3.0-or-later)
-->
GNU GENERAL PUBLIC LICENSE
-333
View File
@@ -1,333 +0,0 @@
# Makefile for Joomla Extensions
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This is a reference Makefile for building Joomla extensions.
# Copy this to your repository root as "Makefile" and customize as needed.
#
# Supports: Modules, Plugins, Components, Packages, Templates
# ==============================================================================
# CONFIGURATION - Customize these for your extension
# ==============================================================================
# Extension Configuration
EXTENSION_NAME := mokosuiteclient
EXTENSION_TYPE := package
# Options: module, plugin, component, package, template
EXTENSION_VERSION := 02.35.00
# Module Configuration (for modules only)
MODULE_TYPE := site
# Options: site, admin
# Plugin Configuration (for plugins only)
PLUGIN_GROUP := system
# Options: system, content, user, authentication, etc.
# Directories
SRC_DIR := .
BUILD_DIR := build
DIST_DIR := dist
DOCS_DIR := docs
# Joomla Installation (for local testing - customize paths)
JOOMLA_ROOT := /var/www/html/joomla
JOOMLA_VERSION := 4
# Tools
PHP := php
COMPOSER := composer
NPM := npm
PHPCS := vendor/bin/phpcs
PHPCBF := vendor/bin/phpcbf
PHPUNIT := vendor/bin/phpunit
ZIP := zip
# Coding Standards
PHPCS_STANDARD := Joomla
# Colors for output
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)║ Joomla Extension Makefile ║$(COLOR_RESET)"
@echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)"
@echo ""
@echo "Extension: $(EXTENSION_NAME) ($(EXTENSION_TYPE)) v$(EXTENSION_VERSION)"
@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 ""
@echo "$(COLOR_YELLOW)Quick Start:$(COLOR_RESET)"
@echo " 1. make install-deps # Install dependencies"
@echo " 2. make build # Build extension package"
@echo " 3. make test # Run tests"
@echo ""
.PHONY: install-deps
install-deps: ## Install all dependencies (Composer + npm)
@echo "$(COLOR_BLUE)Installing dependencies...$(COLOR_RESET)"
@if [ -f "composer.json" ]; then \
$(COMPOSER) install; \
echo "$(COLOR_GREEN)✓ Composer dependencies installed$(COLOR_RESET)"; \
fi
@if [ -f "package.json" ]; then \
$(NPM) install; \
echo "$(COLOR_GREEN)✓ npm dependencies installed$(COLOR_RESET)"; \
fi
.PHONY: update-deps
update-deps: ## Update all dependencies
@echo "$(COLOR_BLUE)Updating dependencies...$(COLOR_RESET)"
@if [ -f "composer.json" ]; then \
$(COMPOSER) update; \
echo "$(COLOR_GREEN)✓ Composer dependencies updated$(COLOR_RESET)"; \
fi
@if [ -f "package.json" ]; then \
$(NPM) update; \
echo "$(COLOR_GREEN)✓ npm dependencies updated$(COLOR_RESET)"; \
fi
.PHONY: lint
lint: ## Run PHP linter (syntax check)
@echo "$(COLOR_BLUE)Running PHP linter...$(COLOR_RESET)"
@find . -name "*.php" ! -path "./vendor/*" ! -path "./node_modules/*" ! -path "./$(BUILD_DIR)/*" \
-exec $(PHP) -l {} \; | grep -v "No syntax errors" || true
@echo "$(COLOR_GREEN)✓ PHP linting complete$(COLOR_RESET)"
.PHONY: phpcs
phpcs: ## Run PHP CodeSniffer (Joomla standards)
@echo "$(COLOR_BLUE)Running PHP CodeSniffer...$(COLOR_RESET)"
@if [ -f "$(PHPCS)" ]; then \
$(PHPCS) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) .; \
else \
echo "$(COLOR_YELLOW)⚠ PHP CodeSniffer not installed. Run: make install-deps$(COLOR_RESET)"; \
fi
.PHONY: phpcbf
phpcbf: ## Fix coding standards automatically
@echo "$(COLOR_BLUE)Running PHP Code Beautifier...$(COLOR_RESET)"
@if [ -f "$(PHPCBF)" ]; then \
$(PHPCBF) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) .; \
echo "$(COLOR_GREEN)✓ Code formatting applied$(COLOR_RESET)"; \
else \
echo "$(COLOR_YELLOW)⚠ PHP Code Beautifier not installed. Run: make install-deps$(COLOR_RESET)"; \
fi
.PHONY: validate
validate: lint phpcs ## Run all validation checks
@echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)"
.PHONY: test
test: ## Run PHPUnit tests
@echo "$(COLOR_BLUE)Running tests...$(COLOR_RESET)"
@if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \
$(PHPUNIT); \
else \
echo "$(COLOR_YELLOW)⚠ PHPUnit not configured$(COLOR_RESET)"; \
fi
.PHONY: test-coverage
test-coverage: ## Run tests with coverage report
@echo "$(COLOR_BLUE)Running tests with coverage...$(COLOR_RESET)"
@if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \
$(PHPUNIT) --coverage-html $(BUILD_DIR)/coverage; \
echo "$(COLOR_GREEN)✓ Coverage report: $(BUILD_DIR)/coverage/index.html$(COLOR_RESET)"; \
else \
echo "$(COLOR_YELLOW)⚠ PHPUnit not configured$(COLOR_RESET)"; \
fi
.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 validate ## Build extension package
@echo "$(COLOR_BLUE)Building Joomla extension package...$(COLOR_RESET)"
@mkdir -p $(DIST_DIR) $(BUILD_DIR)
# Determine package prefix based on extension type
@case "$(EXTENSION_TYPE)" in \
module) \
PACKAGE_PREFIX="mod_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
plugin) \
PACKAGE_PREFIX="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
component) \
PACKAGE_PREFIX="com_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
package) \
PACKAGE_PREFIX="pkg_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
template) \
PACKAGE_PREFIX="tpl_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
*) \
echo "$(COLOR_RED)✗ Unknown extension type: $(EXTENSION_TYPE)$(COLOR_RESET)"; \
exit 1; \
;; \
esac; \
\
mkdir -p "$$BUILD_TARGET"; \
\
echo "Building $$PACKAGE_PREFIX..."; \
\
rsync -av --progress \
--exclude='$(BUILD_DIR)' \
--exclude='$(DIST_DIR)' \
--exclude='.git*' \
--exclude='vendor/' \
--exclude='node_modules/' \
--exclude='tests/' \
--exclude='Makefile' \
--exclude='composer.json' \
--exclude='composer.lock' \
--exclude='package.json' \
--exclude='package-lock.json' \
--exclude='phpunit.xml' \
--exclude='*.md' \
--exclude='.editorconfig' \
. "$$BUILD_TARGET/"; \
\
cd $(BUILD_DIR) && $(ZIP) -r "../$(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip" "$${PACKAGE_PREFIX}"; \
\
echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
.PHONY: package
package: build ## Alias for build
@echo "$(COLOR_GREEN)✓ Package ready for distribution$(COLOR_RESET)"
.PHONY: install-local
install-local: build ## Install to local Joomla (upload via admin)
@echo "$(COLOR_BLUE)Package ready for installation$(COLOR_RESET)"
@case "$(EXTENSION_TYPE)" in \
module) PACKAGE="mod_$(EXTENSION_NAME)";; \
plugin) PACKAGE="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)";; \
component) PACKAGE="com_$(EXTENSION_NAME)";; \
package) PACKAGE="pkg_$(EXTENSION_NAME)";; \
template) PACKAGE="tpl_$(EXTENSION_NAME)";; \
esac; \
echo "$(COLOR_YELLOW)Upload $(DIST_DIR)/$${PACKAGE}-$(EXTENSION_VERSION).zip via Joomla Administrator$(COLOR_RESET)"; \
echo "Admin URL: $(JOOMLA_ROOT) → Extensions → Install"
.PHONY: dev-install
dev-install: ## Create symlink for development (Joomla 4+)
@echo "$(COLOR_BLUE)Creating development symlink...$(COLOR_RESET)"
@if [ ! -d "$(JOOMLA_ROOT)" ]; then \
echo "$(COLOR_RED)✗ Joomla root not found at $(JOOMLA_ROOT)$(COLOR_RESET)"; \
echo "Update JOOMLA_ROOT in Makefile"; \
exit 1; \
fi
@case "$(EXTENSION_TYPE)" in \
module) \
if [ "$(MODULE_TYPE)" = "admin" ]; then \
TARGET="$(JOOMLA_ROOT)/administrator/modules/mod_$(EXTENSION_NAME)"; \
else \
TARGET="$(JOOMLA_ROOT)/modules/mod_$(EXTENSION_NAME)"; \
fi; \
;; \
plugin) \
TARGET="$(JOOMLA_ROOT)/plugins/$(PLUGIN_GROUP)/$(EXTENSION_NAME)"; \
;; \
component) \
echo "$(COLOR_YELLOW)⚠ Components require complex symlink setup$(COLOR_RESET)"; \
echo "Manual setup recommended for component development"; \
exit 1; \
;; \
*) \
echo "$(COLOR_RED)✗ dev-install not supported for $(EXTENSION_TYPE)$(COLOR_RESET)"; \
exit 1; \
;; \
esac; \
\
rm -rf "$$TARGET"; \
ln -s "$(PWD)" "$$TARGET"; \
echo "$(COLOR_GREEN)✓ Development symlink created at $$TARGET$(COLOR_RESET)"
.PHONY: watch
watch: ## Watch for changes and rebuild
@echo "$(COLOR_BLUE)Watching for changes...$(COLOR_RESET)"
@echo "$(COLOR_YELLOW)Press Ctrl+C to stop$(COLOR_RESET)"
@while true; do \
inotifywait -r -e modify,create,delete --exclude '($(BUILD_DIR)|$(DIST_DIR)|vendor|node_modules)' . 2>/dev/null || \
(echo "$(COLOR_YELLOW)⚠ inotifywait not installed. Install: apt-get install inotify-tools$(COLOR_RESET)" && sleep 5); \
make build; \
done
.PHONY: version
version: ## Display version information
@echo "$(COLOR_BLUE)Extension Information:$(COLOR_RESET)"
@echo " Name: $(EXTENSION_NAME)"
@echo " Type: $(EXTENSION_TYPE)"
@echo " Version: $(EXTENSION_VERSION)"
@if [ "$(EXTENSION_TYPE)" = "module" ]; then \
echo " Module: $(MODULE_TYPE)"; \
fi
@if [ "$(EXTENSION_TYPE)" = "plugin" ]; then \
echo " Group: $(PLUGIN_GROUP)"; \
fi
.PHONY: docs
docs: ## Generate documentation
@echo "$(COLOR_BLUE)Generating documentation...$(COLOR_RESET)"
@mkdir -p $(DOCS_DIR)
@echo "$(COLOR_YELLOW)⚠ Documentation generation not configured$(COLOR_RESET)"
@echo "Consider adding phpDocumentor or similar"
.PHONY: release
release: validate test build ## Create a release (validate + test + build)
@echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)"
@echo ""
@echo "$(COLOR_BLUE)Release Checklist:$(COLOR_RESET)"
@echo " [ ] Update CHANGELOG.md"
@echo " [ ] Update version in XML manifest"
@echo " [ ] Test installation in clean Joomla"
@echo " [ ] Tag release in git: git tag v$(EXTENSION_VERSION)"
@echo " [ ] Push tags: git push --tags"
@echo " [ ] Create GitHub release"
@echo ""
@case "$(EXTENSION_TYPE)" in \
module) PACKAGE="mod_$(EXTENSION_NAME)";; \
plugin) PACKAGE="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)";; \
component) PACKAGE="com_$(EXTENSION_NAME)";; \
package) PACKAGE="pkg_$(EXTENSION_NAME)";; \
template) PACKAGE="tpl_$(EXTENSION_NAME)";; \
esac; \
echo "$(COLOR_GREEN)Package: $(DIST_DIR)/$${PACKAGE}-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
.PHONY: security-check
security-check: ## Run security checks on dependencies
@echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)"
@if [ -f "composer.json" ]; then \
$(COMPOSER) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \
fi
@if [ -f "package.json" ]; then \
$(NPM) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \
fi
.PHONY: all
all: install-deps validate test build ## Run complete build pipeline
@echo "$(COLOR_GREEN)✓ Complete build pipeline finished$(COLOR_RESET)"
# Default target
.DEFAULT_GOAL := help
+1 -1
View File
@@ -9,7 +9,7 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /README.md
BRIEF: MokoSuiteClient platform plugin for Joomla
-->
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
INGROUP: [PROJECT_NAME].Documentation
REPO: [REPOSITORY_URL]
PATH: /SECURITY.md
VERSION: 02.48.16
VERSION: 02.50.00
BRIEF: Security vulnerability reporting and handling policy
-->
-237
View File
@@ -1,237 +0,0 @@
#!/usr/bin/env bash
# ============================================================================
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Automation.CI
# INGROUP: moko-platform.Automation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /automation/ci-issue-reporter.sh
# VERSION: 09.23.00
# BRIEF: Creates or updates a Gitea issue when a CI gate fails.
# Deduplicates by searching open issues with the "ci-auto" label
# whose title matches the gate. If a matching issue exists, a comment
# is appended instead of opening a duplicate.
# ============================================================================
set -euo pipefail
# ── Defaults ────────────────────────────────────────────────────────────────
GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}"
GITEA_TOKEN="${GITEA_TOKEN:-}"
REPO="${GITHUB_REPOSITORY:-}"
RUN_URL="${GITHUB_SERVER_URL:-${GITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}"
LABEL_NAME="ci-auto"
LABEL_COLOR="#e11d48"
GATE=""
DETAILS=""
SEVERITY="error"
WORKFLOW=""
# ── Parse arguments ─────────────────────────────────────────────────────────
usage() {
cat <<EOF
Usage: ci-issue-reporter.sh --gate NAME --details TEXT [OPTIONS]
Required:
--gate CI gate name (e.g. "Code Quality", "Self-Health")
--details Human-readable failure description
Optional:
--severity "error" (default) or "warning"
--workflow Workflow name for the issue title
--repo owner/repo (default: \$GITHUB_REPOSITORY)
--run-url URL to the CI run (auto-detected from env)
--token Gitea API token (default: \$GITEA_TOKEN)
--url Gitea base URL (default: \$GITEA_URL)
EOF
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--gate) GATE="$2"; shift 2 ;;
--details) DETAILS="$2"; shift 2 ;;
--severity) SEVERITY="$2"; shift 2 ;;
--workflow) WORKFLOW="$2"; shift 2 ;;
--repo) REPO="$2"; shift 2 ;;
--run-url) RUN_URL="$2"; shift 2 ;;
--token) GITEA_TOKEN="$2"; shift 2 ;;
--url) GITEA_URL="$2"; shift 2 ;;
-h|--help) usage ;;
*) echo "Unknown option: $1"; usage ;;
esac
done
[[ -z "$GATE" ]] && { echo "ERROR: --gate is required"; usage; }
[[ -z "$DETAILS" ]] && { echo "ERROR: --details is required"; usage; }
[[ -z "$GITEA_TOKEN" ]] && { echo "ERROR: GITEA_TOKEN not set"; exit 1; }
[[ -z "$REPO" ]] && { echo "ERROR: GITHUB_REPOSITORY not set"; exit 1; }
API="${GITEA_URL}/api/v1/repos/${REPO}"
# ── Build title ─────────────────────────────────────────────────────────────
if [[ -n "$WORKFLOW" ]]; then
TITLE="[CI] ${WORKFLOW}: ${GATE} failed"
else
TITLE="[CI] ${GATE} failed"
fi
# ── Ensure label exists ─────────────────────────────────────────────────────
ensure_label() {
local exists
exists=$(curl -sf -o /dev/null -w '%{http_code}' \
-H "Authorization: token ${GITEA_TOKEN}" \
"${API}/labels" 2>/dev/null || echo "000")
if [[ "$exists" == "200" ]]; then
# Check if label already exists
local found
found=$(curl -sf \
-H "Authorization: token ${GITEA_TOKEN}" \
"${API}/labels" 2>/dev/null \
| grep -o "\"name\":\"${LABEL_NAME}\"" || true)
if [[ -z "$found" ]]; then
curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/labels" \
-d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \
> /dev/null 2>&1 || true
fi
fi
}
# ── Search for existing open issue ──────────────────────────────────────────
find_existing_issue() {
# URL-encode the gate name for the query
local query
query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g')
local response
response=$(curl -sf \
-H "Authorization: token ${GITEA_TOKEN}" \
"${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \
2>/dev/null || echo "[]")
# Extract the first matching issue number
echo "$response" \
| grep -oP '"number":\s*\K[0-9]+' \
| head -1
}
# ── Build issue body ────────────────────────────────────────────────────────
build_body() {
local severity_badge
if [[ "$SEVERITY" == "error" ]]; then
severity_badge="**Severity:** Error"
else
severity_badge="**Severity:** Warning"
fi
cat <<BODY
## CI Gate Failure: ${GATE}
${severity_badge}
**Workflow:** ${WORKFLOW:-unknown}
**Branch:** ${GITHUB_REF_NAME:-unknown}
**Commit:** \`${GITHUB_SHA:0:8}\`
**Run:** [View CI run](${RUN_URL})
### Details
${DETAILS}
### Resolution
Fix the issue described above and push a new commit. This issue will be closed automatically when the gate passes, or can be closed manually.
---
*Auto-created by [ci-issue-reporter](${GITEA_URL}/${REPO}/src/branch/main/automation/ci-issue-reporter.sh)*
BODY
}
# ── Build comment body (for existing issues) ────────────────────────────────
build_comment() {
cat <<COMMENT
### CI failure recurrence
**Branch:** ${GITHUB_REF_NAME:-unknown}
**Commit:** \`${GITHUB_SHA:0:8}\`
**Run:** [View CI run](${RUN_URL})
${DETAILS}
COMMENT
}
# ── Main ────────────────────────────────────────────────────────────────────
ensure_label
EXISTING=$(find_existing_issue)
if [[ -n "$EXISTING" ]]; then
# Append comment to existing issue
COMMENT_BODY=$(build_comment)
COMMENT_JSON=$(printf '%s' "$COMMENT_BODY" | python3 -c "
import sys, json
print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null)
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/issues/${EXISTING}/comments" \
-d "${COMMENT_JSON}" 2>/dev/null || echo "000")
if [[ "$HTTP" == "201" ]]; then
echo "Commented on existing issue #${EXISTING}"
else
echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})"
fi
else
# Create new issue
ISSUE_BODY=$(build_body)
ISSUE_JSON=$(python3 -c "
import sys, json
body = sys.stdin.read()
print(json.dumps({
'title': sys.argv[1],
'body': body,
'labels': []
}))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null)
# Create the issue
RESPONSE=$(curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/issues" \
-d "${ISSUE_JSON}" 2>/dev/null || echo "{}")
ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1)
if [[ -n "$ISSUE_NUM" ]]; then
# Apply label (separate call — more reliable across Gitea versions)
LABEL_ID=$(curl -sf \
-H "Authorization: token ${GITEA_TOKEN}" \
"${API}/labels" 2>/dev/null \
| grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \
| head -1 || true)
if [[ -n "$LABEL_ID" ]]; then
curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${API}/issues/${ISSUE_NUM}/labels" \
-d "{\"labels\":[${LABEL_ID}]}" \
> /dev/null 2>&1 || true
fi
echo "Created issue #${ISSUE_NUM}: ${TITLE}"
else
echo "WARNING: Failed to create issue"
echo "Response: ${RESPONSE}"
fi
fi
+2 -2
View File
@@ -11,13 +11,13 @@
INGROUP: MokoSuiteClient.Build
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
FILE: build-guide.md
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /docs/guides/
BRIEF: Build and packaging guide for the MokoSuiteClient system plugin
NOTE: Defines environment setup, repository layout, packaging rules, and release preparation
-->
# MokoSuiteClient Build Guide (VERSION: 02.48.16)
# MokoSuiteClient Build Guide (VERSION: 02.50.00)
## 1. Purpose
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /docs/guides/configuration-guide.md
BRIEF: Configuration guide for the MokoSuiteClient system plugin
NOTE: Defines plugin parameters, expected behaviors, and recommended defaults
-->
# MokoSuiteClient Configuration Guide (VERSION: 02.48.16)
# MokoSuiteClient Configuration Guide (VERSION: 02.50.00)
## 1. Objective
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /docs/guides/installation-guide.md
BRIEF: Installation guide for the MokoSuiteClient system plugin
NOTE: First document in the guide set
-->
# MokoSuiteClient Installation Guide (VERSION: 02.48.16)
# MokoSuiteClient Installation Guide (VERSION: 02.50.00)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /docs/guides/operations-guide.md
BRIEF: Operational guide for administering and managing the MokoSuiteClient system plugin
NOTE: Defines lifecycle, responsibilities, and operational behaviors
-->
# MokoSuiteClient Operations Guide (VERSION: 02.48.16)
# MokoSuiteClient Operations Guide (VERSION: 02.50.00)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /docs/guides/rollback-and-recovery-guide.md
BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents
NOTE: Completes the core guide set for Suite plugin governance
-->
# MokoSuiteClient Rollback and Recovery Guide (VERSION: 02.48.16)
# MokoSuiteClient Rollback and Recovery Guide (VERSION: 02.50.00)
## Introduction
+2 -2
View File
@@ -7,13 +7,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /docs/guides/testing-guide.md
BRIEF: Testing guide for MokoSuiteClient v02.01.08
NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration
-->
# MokoSuiteClient Testing Guide (VERSION: 02.48.16)
# MokoSuiteClient Testing Guide (VERSION: 02.50.00)
## 1. Prerequisites
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /docs/guides/troubleshooting-guide.md
BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoSuiteClient plugin
NOTE: Designed for administrators and Suite operations teams
-->
# MokoSuiteClient Troubleshooting Guide (VERSION: 02.48.16)
# MokoSuiteClient Troubleshooting Guide (VERSION: 02.50.00)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Guides
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /docs/guides/upgrade-and-versioning-guide.md
BRIEF: Guide for updating, versioning, and maintaining the MokoSuiteClient plugin
NOTE: Defines release flow, version rules, and upgrade validation
-->
# MokoSuiteClient Upgrade and Versioning Guide (VERSION: 02.48.16)
# MokoSuiteClient Upgrade and Versioning Guide (VERSION: 02.50.00)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoSuiteClient.Documentation
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
VERSION: 02.48.16
VERSION: 02.50.00
PATH: /docs/index.md
BRIEF: Master index of all documentation for the MokoSuiteClient plugin
NOTE: Automatically maintained index for all guide canvases
-->
# MokoSuiteClient Documentation Index (VERSION: 02.48.16)
# MokoSuiteClient Documentation Index (VERSION: 02.50.00)
## Introduction
+2 -2
View File
@@ -11,12 +11,12 @@
INGROUP: MokoSuiteClient
REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
PATH: /docs/plugin-basic.md
VERSION: 02.48.16
VERSION: 02.50.00
BRIEF: Baseline documentation for the MokoSuiteClient system plugin
NOTE: Foundational reference for internal and external stakeholders
-->
# MokoSuiteClient Plugin Overview (VERSION: 02.48.16)
# MokoSuiteClient Plugin Overview (VERSION: 02.50.00)
## Introduction
+1 -1
View File
@@ -10,7 +10,7 @@ DEFGROUP: MokoSuiteClient.Documentation
INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoSuiteClient
PATH: /docs/update-server.md
VERSION: 02.48.16
VERSION: 02.50.00
BRIEF: How this extension's Joomla update server file (update.xml) is managed
-->
@@ -0,0 +1,108 @@
-- Regular Labs replacement tables (conditions, snippets, replacements, content templates)
-- These were in install.mysql.sql but missing from updates, so existing installs never got them.
CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_conditions` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`alias` VARCHAR(100) NOT NULL DEFAULT '',
`name` VARCHAR(100) NOT NULL DEFAULT '',
`description` TEXT NOT NULL,
`category` VARCHAR(50) NOT NULL DEFAULT '',
`color` VARCHAR(8) DEFAULT NULL,
`match_all` TINYINT(1) NOT NULL DEFAULT 1,
`published` TINYINT(1) NOT NULL DEFAULT 1,
`hash` VARCHAR(32) NOT NULL DEFAULT '',
`checked_out` INT UNSIGNED DEFAULT NULL,
`checked_out_time` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_published` (`published`),
KEY `idx_alias` (`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_conditions_groups` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`condition_id` INT UNSIGNED NOT NULL,
`match_all` TINYINT(1) NOT NULL DEFAULT 1,
`ordering` INT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_condition` (`condition_id`),
KEY `idx_ordering` (`condition_id`, `ordering`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_conditions_rules` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`group_id` INT UNSIGNED NOT NULL,
`type` VARCHAR(50) NOT NULL DEFAULT '',
`exclude` TINYINT(1) NOT NULL DEFAULT 0,
`params` TEXT NOT NULL,
`ordering` INT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_group` (`group_id`),
KEY `idx_type` (`type`),
KEY `idx_ordering` (`group_id`, `ordering`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_conditions_map` (
`condition_id` INT UNSIGNED NOT NULL,
`extension` VARCHAR(50) NOT NULL DEFAULT '',
`item_id` INT UNSIGNED NOT NULL DEFAULT 0,
UNIQUE KEY `idx_unique` (`condition_id`, `item_id`, `extension`),
KEY `idx_ext_item` (`extension`, `item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_snippets` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`alias` VARCHAR(100) NOT NULL DEFAULT '',
`name` VARCHAR(100) NOT NULL DEFAULT '',
`description` TEXT NOT NULL,
`category` VARCHAR(50) NOT NULL DEFAULT '',
`color` VARCHAR(8) DEFAULT NULL,
`content` MEDIUMTEXT NOT NULL,
`params` TEXT NOT NULL,
`published` TINYINT(1) NOT NULL DEFAULT 0,
`ordering` INT NOT NULL DEFAULT 0,
`checked_out` INT UNSIGNED DEFAULT NULL,
`checked_out_time` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_alias` (`alias`),
KEY `idx_published` (`published`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_replacements` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL DEFAULT '',
`search` TEXT NOT NULL,
`replace_value` TEXT NOT NULL,
`area` VARCHAR(20) NOT NULL DEFAULT 'both',
`regex` TINYINT(1) NOT NULL DEFAULT 0,
`casesensitive` TINYINT(1) NOT NULL DEFAULT 0,
`category` VARCHAR(50) NOT NULL DEFAULT '',
`published` TINYINT(1) NOT NULL DEFAULT 0,
`description` TEXT NOT NULL,
`enable_in_admin` TINYINT(1) NOT NULL DEFAULT 0,
`color` VARCHAR(8) DEFAULT NULL,
`ordering` INT NOT NULL DEFAULT 0,
`checked_out` INT UNSIGNED DEFAULT NULL,
`checked_out_time` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_published` (`published`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_content_templates` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`alias` VARCHAR(100) NOT NULL DEFAULT '',
`name` VARCHAR(255) NOT NULL DEFAULT '',
`description` TEXT NOT NULL,
`category` VARCHAR(50) NOT NULL DEFAULT '',
`color` VARCHAR(8) DEFAULT NULL,
`template_data` MEDIUMTEXT NOT NULL,
`joomla_category_id` INT NOT NULL DEFAULT 0,
`access` INT UNSIGNED NOT NULL DEFAULT 1,
`published` TINYINT(1) NOT NULL DEFAULT 1,
`ordering` INT NOT NULL DEFAULT 0,
`checked_out` INT UNSIGNED DEFAULT NULL,
`checked_out_time` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_published` (`published`),
KEY `idx_alias` (`alias`),
KEY `idx_category` (`joomla_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -108,13 +108,6 @@ class SupportPinHelper
return 'MOKO-' . strtoupper(substr($hash, 0, 4)) . '-' . strtoupper(substr($hash, 4, 4));
}
/**
* Request a new PIN: stamps the current time into plugin params and returns the PIN.
*
* @param DatabaseInterface $db Database driver.
*
* @return array{success: bool, pin?: string, message: string}
*/
/**
* Render PIN badge HTML (active PIN with copy, or request button).
*
@@ -198,7 +191,7 @@ class SupportPinHelper
navigator.clipboard.writeText(pin).then(function() {
if (textEl) {
textEl.textContent = 'Copied!';
setTimeout(function() { textEl.textContent = pin; }, 30000);
setTimeout(function() { textEl.textContent = pin; }, 5000);
}
});
}
@@ -257,6 +250,13 @@ class SupportPinHelper
JS;
}
/**
* Request a new PIN: stamps the current time into plugin params and returns the PIN.
*
* @param DatabaseInterface $db Database driver.
*
* @return array{success: bool, pin?: string, message: string}
*/
public static function requestNew(DatabaseInterface $db): array
{
$state = self::getState($db);
@@ -44,15 +44,16 @@ class HtmlView extends BaseHtmlView
$this->supportPinAvailable = $pinState['available'];
$this->supportPin = $pinState['pin'];
// Detect Regular Labs data for import
// Detect Regular Labs data for import (source table must exist AND our destination table)
try {
$rlDb = \Joomla\CMS\Factory::getDbo();
$rlDb = \Joomla\CMS\Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$rlTables = $rlDb->getTableList();
$rlPrefix = $rlDb->getPrefix();
$this->regularLabsAvailable = in_array($rlPrefix . 'conditions', $rlTables)
|| in_array($rlPrefix . 'snippets', $rlTables)
|| in_array($rlPrefix . 'rereplacer', $rlTables)
|| in_array($rlPrefix . 'contenttemplater', $rlTables);
$this->regularLabsAvailable =
(in_array($rlPrefix . 'conditions', $rlTables) && in_array($rlPrefix . 'mokosuiteclient_conditions', $rlTables))
|| (in_array($rlPrefix . 'snippets', $rlTables) && in_array($rlPrefix . 'mokosuiteclient_snippets', $rlTables))
|| (in_array($rlPrefix . 'rereplacer', $rlTables) && in_array($rlPrefix . 'mokosuiteclient_replacements', $rlTables))
|| (in_array($rlPrefix . 'contenttemplater', $rlTables) && in_array($rlPrefix . 'mokosuiteclient_content_templates', $rlTables));
} catch (\Throwable $e) {}
$this->recentLogins = $model->getRecentLogins(10);
@@ -20,7 +20,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>MokoSuiteClient admin dashboard and REST API. Provides a control panel for managing MokoSuiteClient feature plugins, site health monitoring, and remote management endpoints.</description>
<namespace path="src">Moko\Component\MokoSuiteClient</namespace>
@@ -74,10 +74,6 @@
<folder>tmpl</folder>
</files>
<install>
<sql><file driver="mysql" charset="utf8">admin/sql/install.mysql.sql</file></sql>
</install>
<api>
<files folder="api">
<folder>src</folder>
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>MOD_MOKOSUITECLIENT_CACHE_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteClientCache</namespace>
@@ -1,4 +1,13 @@
<?php
/**
* @package MokoSuiteClient
* @subpackage mod_mokosuiteclient_cache
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Moko\Module\MokoSuiteClientCache\Administrator\Dispatcher;
defined('_JEXEC') or die;
@@ -1,8 +1,8 @@
<?php
/**
* MokoSuiteClient Cache & Temp Cleaner status bar split button
* MokoSuiteClient Cache & Temp Cleaner status bar with Clean dropdown
*
* 4 buttons: Frontend link | Support PIN | Clear Cache | Clear Temp
* Buttons: Frontend link | Support PIN | Clean (Cache / Temp)
*/
defined('_JEXEC') or die;
@@ -34,12 +34,23 @@ $frontendUrl = $frontendUrl ?? '';
}
echo $pinHtml;
endif; ?>
<a href="#" class="btn btn-sm btn-outline-primary <?php echo ($pinAvailable || $frontendUrl) ? 'rounded-0 border-end-0' : 'rounded-0 rounded-start border-end-0'; ?> d-flex align-items-center gap-1 px-3 py-2" id="mokosuiteclient-clear-cache" title="Clear all Joomla cache" style="font-size:0.8rem;">
<span class="icon-bolt" aria-hidden="true" id="mokosuiteclient-cache-icon"></span> Cache
</a>
<a href="#" class="btn btn-sm btn-outline-danger rounded-0 rounded-end d-flex align-items-center gap-1 px-3 py-2" id="mokosuiteclient-clear-temp" title="Clear temp directory" style="font-size:0.8rem;">
<span class="icon-trash" aria-hidden="true" id="mokosuiteclient-temp-icon"></span> Temp
</a>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary <?php echo ($pinAvailable || $frontendUrl) ? 'rounded-0 rounded-end' : 'rounded'; ?> d-flex align-items-center gap-1 px-3 py-2 dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" style="font-size:0.8rem;" id="mokosuite-clean-dropdown">
<span class="icon-broom" aria-hidden="true"></span> Clean
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="mokosuite-clean-dropdown">
<li>
<a href="#" class="dropdown-item d-flex align-items-center gap-2" id="mokosuiteclient-clear-cache">
<span class="icon-bolt" aria-hidden="true" id="mokosuiteclient-cache-icon"></span> Clear Cache
</a>
</li>
<li>
<a href="#" class="dropdown-item d-flex align-items-center gap-2" id="mokosuiteclient-clear-temp">
<span class="icon-trash" aria-hidden="true" id="mokosuiteclient-temp-icon"></span> Clear Temp
</a>
</li>
</ul>
</div>
</div>
</div>
@@ -94,7 +105,6 @@ document.addEventListener('DOMContentLoaded', function() {
setupCleaner('mokosuiteclient-clear-cache', 'mokosuiteclient-cache-icon', '<?php echo $cacheUrl; ?>', '<?php echo $token; ?>');
setupCleaner('mokosuiteclient-clear-temp', 'mokosuiteclient-temp-icon', '<?php echo $tempUrl; ?>', '<?php echo $token; ?>');
});
</script>
<?php echo \Moko\Component\MokoSuiteClient\Administrator\Helper\SupportPinHelper::renderScript(); ?>
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>MOD_MOKOSUITECLIENT_CATEGORIES_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteClientCategories</namespace>
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>MOD_MOKOSUITECLIENT_CPANEL_DESC</description>
<namespace path="src">Moko\Module\MokoSuiteClientCpanel</namespace>
@@ -1,9 +1,6 @@
<?php
/**
* @package MokoSuiteClient
* @subpackage mod_mokosuiteclient_cpanel
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* MokoSuiteClient Cpanel slim bar with dropdown detail panel
*/
defined('_JEXEC') or die;
@@ -13,35 +10,21 @@ use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
// Hidden when on MokoSuiteClient dashboard (redundant info)
if (!empty($hidden)) return;
$siteInfo = $siteInfo ?? (object) [];
$plugins = $plugins ?? [];
$healthOk = $healthOk ?? true;
$counts = $counts ?? (object) ['articles' => 0, 'users' => 0, 'extensions' => 0, 'updates' => 0];
$disk = $disk ?? (object) ['free_mb' => null, 'total_mb' => null];
$currentIp = $currentIp ?? '';
$collapsed = true;
$showHealth = $params->get('show_health', 1);
$showStats = $params->get('show_stats', 1);
$showDisk = $params->get('show_disk', 1);
$showIp = $params->get('show_ip', 1);
$showPlugins = $params->get('show_plugins', 1);
$showActions = $params->get('show_actions', 1);
$showVersions = $params->get('show_versions', 1);
$token = Session::getFormToken();
$token = Session::getFormToken();
$showPlugins = $params->get('show_plugins', 1);
$enabledCount = 0;
$totalCount = count($plugins);
foreach ($plugins as $p)
{
if ($p->enabled)
{
$enabledCount++;
}
foreach ($plugins as $p) {
if ($p->enabled) $enabledCount++;
}
$labels = [
@@ -52,41 +35,106 @@ $labels = [
'mokosuiteclient_offline' => 'Offline Bypass',
'mokosuiteclient_dbip' => 'GeoIP Lookup',
'mokosuiteclient_license' => 'License Manager',
'mokosuiteclient_backup' => 'Backup Bridge',
];
$diskPct = ($disk->total_mb && $disk->total_mb > 0)
? round((($disk->total_mb - ($disk->free_mb ?? 0)) / $disk->total_mb) * 100)
: null;
$diskColor = ($diskPct !== null && $diskPct > 90) ? 'bg-danger' : (($diskPct !== null && $diskPct > 75) ? 'bg-warning' : 'bg-success');
$diskPct = ($disk->total_mb && $disk->total_mb > 0)
? round((($disk->total_mb - ($disk->free_mb ?? 0)) / $disk->total_mb) * 100) : null;
$diskColor = ($diskPct !== null && $diskPct > 90) ? 'danger' : (($diskPct !== null && $diskPct > 75) ? 'warning' : 'success');
$canDashboard = Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_mokosuiteclient');
$siteName = htmlspecialchars($siteInfo->sitename ?? '', ENT_QUOTES, 'UTF-8');
$mokoVer = htmlspecialchars($siteInfo->mokosuiteclient_version ?? '', ENT_QUOTES, 'UTF-8');
$joomlaVer = htmlspecialchars($siteInfo->joomla_version ?? '', ENT_QUOTES, 'UTF-8');
$phpVer = htmlspecialchars($siteInfo->php_version ?? '', ENT_QUOTES, 'UTF-8');
$dbType = htmlspecialchars($siteInfo->db_type ?? '', ENT_QUOTES, 'UTF-8');
$ipEscaped = htmlspecialchars($currentIp, ENT_QUOTES, 'UTF-8');
$statusDots = [];
if (!empty($siteInfo->debug)) $statusDots[] = '<span class="badge bg-warning text-dark" style="font-size:0.7rem">Debug</span>';
if (!empty($siteInfo->offline)) $statusDots[] = '<span class="badge bg-danger" style="font-size:0.7rem">Offline</span>';
if (($counts->updates ?? 0) > 0) $statusDots[] = '<span class="badge bg-info" style="font-size:0.7rem">' . (int)$counts->updates . ' updates</span>';
?>
<div class="mod-mokosuiteclient-cpanel card p-3 mb-4">
<div class="d-flex flex-wrap align-items-center gap-2" style="font-size:0.85rem;">
<?php $canDashboard = Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_mokosuiteclient'); ?>
<div class="mod-mokosuiteclient-cpanel mb-2" style="font-size:0.82rem;">
<div class="d-flex align-items-center gap-2 px-3 py-1 rounded" style="background:linear-gradient(135deg,#f8f9fa 0%,#e9ecef 100%);border:1px solid #dee2e6;">
<?php if ($canDashboard): ?>
<a href="<?php echo Route::_('index.php?option=com_mokosuiteclient'); ?>" style="color:#1a2744;text-decoration:none;" title="MokoSuite Dashboard"><span class="icon-shield-alt" aria-hidden="true" style="font-size:1.1rem"></span></a>
<a href="<?php echo Route::_('index.php?option=com_mokosuiteclient'); ?>" style="color:#1a2744;text-decoration:none;" title="MokoSuite Dashboard">
<span class="icon-shield-alt" aria-hidden="true" style="font-size:1rem"></span>
</a>
<?php else: ?>
<span class="icon-shield-alt" aria-hidden="true" style="font-size:1.1rem;color:#1a2744"></span>
<span class="icon-shield-alt" aria-hidden="true" style="font-size:1rem;color:#1a2744"></span>
<?php endif; ?>
<span class="fw-bold"><?php echo htmlspecialchars($siteInfo->sitename ?? ''); ?></span>
<span class="badge bg-primary">MokoSuite <?php echo htmlspecialchars($siteInfo->mokosuiteclient_version ?? ''); ?></span>
<span class="fw-semibold"><?php echo $siteName; ?></span>
<span class="text-muted" style="font-size:0.75rem">v<?php echo $mokoVer; ?></span>
<?php echo implode(' ', $statusDots); ?>
<?php echo \Moko\Component\MokoSuiteClient\Administrator\Helper\SupportPinHelper::renderBadge(
['available' => !empty($supportPinAvailable), 'pin' => $supportPin ?? ''],
$token, 'cpanel'
); ?>
<span class="badge bg-secondary">Joomla <?php echo htmlspecialchars($siteInfo->joomla_version ?? ''); ?></span>
<span class="badge bg-secondary">PHP <?php echo htmlspecialchars($siteInfo->php_version ?? ''); ?></span>
<span class="badge bg-secondary"><?php echo htmlspecialchars($siteInfo->db_type ?? ''); ?></span>
<?php if (!empty($siteInfo->debug)): ?>
<span class="badge bg-warning text-dark">Debug ON</span>
<?php endif; ?>
<?php if (!empty($siteInfo->offline)): ?>
<span class="badge bg-danger">Offline</span>
<?php endif; ?>
<span class="ms-auto d-flex align-items-center gap-2">
<span class="icon-globe" aria-hidden="true"></span>
<code><?php echo htmlspecialchars($currentIp); ?></code>
<span class="text-muted" style="font-size:0.75rem">
<span class="icon-globe" aria-hidden="true"></span> <code style="font-size:0.75rem"><?php echo $ipEscaped; ?></code>
</span>
<button class="btn btn-sm btn-link text-muted p-0 ms-1" type="button" data-bs-toggle="collapse" data-bs-target="#mokosuite-cpanel-detail" aria-expanded="false" aria-controls="mokosuite-cpanel-detail" title="Show details" style="font-size:0.85rem;text-decoration:none;">
<span class="icon-chevron-down mokosuite-cpanel-chevron" aria-hidden="true" style="transition:transform 0.2s"></span>
</button>
</span>
</div>
<div class="collapse" id="mokosuite-cpanel-detail">
<div class="card card-body mt-1 p-3" style="font-size:0.82rem;border-color:#dee2e6;">
<div class="row g-3">
<div class="col-md-4">
<h6 class="text-muted mb-2" style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.05em">Environment</h6>
<div class="d-flex flex-wrap gap-1">
<span class="badge bg-primary">MokoSuite <?php echo $mokoVer; ?></span>
<span class="badge bg-secondary">Joomla <?php echo $joomlaVer; ?></span>
<span class="badge bg-secondary">PHP <?php echo $phpVer; ?></span>
<span class="badge bg-secondary"><?php echo $dbType; ?></span>
</div>
</div>
<div class="col-md-4">
<h6 class="text-muted mb-2" style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.05em">Stats</h6>
<div class="d-flex flex-wrap gap-2" style="font-size:0.8rem">
<span><span class="icon-file-alt" aria-hidden="true"></span> <?php echo (int)($counts->articles ?? 0); ?> articles</span>
<span><span class="icon-users" aria-hidden="true"></span> <?php echo (int)($counts->users ?? 0); ?> users</span>
<span><span class="icon-puzzle-piece" aria-hidden="true"></span> <?php echo (int)($counts->extensions ?? 0); ?> extensions</span>
</div>
<?php if ($diskPct !== null): ?>
<div class="mt-1">
<div class="progress" style="height:4px">
<div class="progress-bar bg-<?php echo $diskColor; ?>" style="width:<?php echo $diskPct; ?>%"></div>
</div>
<small class="text-muted">Disk <?php echo $diskPct; ?>% (<?php echo number_format(($disk->free_mb ?? 0) / 1024, 1); ?> GB free)</small>
</div>
<?php endif; ?>
</div>
<?php if ($showPlugins && $totalCount > 0): ?>
<div class="col-md-4">
<h6 class="text-muted mb-2" style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.05em">Plugins (<?php echo $enabledCount; ?>/<?php echo $totalCount; ?>)</h6>
<div class="d-flex flex-wrap gap-1">
<?php foreach ($plugins as $p):
$el = str_replace('plg_system_', '', $p->element);
$label = $labels[$el] ?? ucfirst(str_replace('mokosuiteclient_', '', $el));
$color = $p->enabled ? 'success' : 'secondary';
?>
<span class="badge bg-<?php echo $color; ?>" style="font-size:0.7rem"><?php echo htmlspecialchars($label); ?></span>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<style>
[aria-expanded="true"] .mokosuite-cpanel-chevron { transform: rotate(180deg); }
</style>
<?php echo \Moko\Component\MokoSuiteClient\Administrator\Helper\SupportPinHelper::renderScript(); ?>
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>MokoSuiteClient admin sidebar menu — renders a dedicated MokoSuiteClient section in the admin menu before Joomla's default menu.</description>
<namespace path="src">Moko\Module\MokoSuiteClientMenu</namespace>
@@ -2,9 +2,8 @@
/**
* MokoSuiteClient Admin Sidebar Menu
*
* Each installed Moko component gets its own top-level collapsible section.
* com_mokosuitehq is always pinned first. com_mokosuiteclient uses static views
* as children. All other components auto-discover their submenu items.
* Single "MokoSuite" top-level item with all Moko ecosystem components
* as collapsible children underneath.
*/
defined('_JEXEC') or die;
@@ -38,6 +37,72 @@ $mokosuiteclientStaticViews = array_filter($allViews, function ($v) use ($user,
return $isSuper || $user->authorise($v['acl'], 'com_mokosuiteclient');
});
// ── Icon overrides (canonical icons per component) ───────────────────
$iconOverrides = [
'com_mokosuiteclient' => 'icon-shield-alt',
'com_mokosuitehq' => 'icon-tachometer-alt',
'com_mokosuitebackup' => 'icon-archive',
'com_mokosuitecrm' => 'icon-address-book',
'com_mokosuiteerp' => 'icon-briefcase',
'com_mokosuiteshop' => 'icon-shopping-cart',
'com_mokosuitepos' => 'icon-calculator',
'com_mokosuitemrp' => 'icon-cogs',
'com_mokosuitehrm' => 'icon-id-badge',
'com_mokosuiterestaurant' => 'icon-utensils',
'com_mokosuitechild' => 'icon-child',
'com_mokosuitenpo' => 'icon-heart',
'com_mokosuitefield' => 'icon-wrench',
'com_mokosuitecreate' => 'icon-paint-brush',
'com_mokosuiteforms' => 'icon-list-alt',
'com_mokosuitecommunity' => 'icon-comments',
'com_mokosuitecross' => 'icon-share-alt',
'com_mokosuiteopengraph' => 'icon-globe',
'com_mokosuitestorelocator' => 'icon-map-marker-alt',
];
$childIconMap = [
'dashboard' => 'icon-tachometer-alt',
'contacts' => 'icon-address-book',
'deals' => 'icon-handshake',
'activities' => 'icon-clock',
'tickets' => 'icon-life-ring',
'helpdesk' => 'icon-life-ring',
'products' => 'icon-box',
'orders' => 'icon-shopping-cart',
'invoices' => 'icon-file-invoice',
'inventory' => 'icon-warehouse',
'settings' => 'icon-cog',
'config' => 'icon-cog',
'options' => 'icon-sliders-h',
'reports' => 'icon-chart-bar',
'analytics' => 'icon-chart-line',
'locations' => 'icon-map-marker-alt',
'stores' => 'icon-store',
'categories' => 'icon-folder',
'forms' => 'icon-list-alt',
'submissions' => 'icon-inbox',
'emails' => 'icon-envelope',
'campaigns' => 'icon-bullhorn',
'users' => 'icon-users',
'members' => 'icon-id-card',
'employees' => 'icon-id-badge',
'donors' => 'icon-hand-holding-heart',
'donations' => 'icon-donate',
'events' => 'icon-calendar',
'tasks' => 'icon-tasks',
'projects' => 'icon-project-diagram',
'templates' => 'icon-file-alt',
'announcements'=> 'icon-bullhorn',
'plugins' => 'icon-plug',
'import' => 'icon-upload',
'export' => 'icon-download',
'log' => 'icon-list',
'backup' => 'icon-archive',
'channels' => 'icon-share-alt',
'posts' => 'icon-paper-plane',
'schedule' => 'icon-calendar-alt',
];
// ── Auto-discover all Moko components from #__menu ──────────────────
$mokoComponents = [];
@@ -56,7 +121,6 @@ try
);
$menuItems = $db->loadObjectList() ?: [];
// Load language files for discovered components
$lang = Factory::getLanguage();
$loadedLangs = [];
foreach ($menuItems as $m)
@@ -65,47 +129,50 @@ try
{
$lang->load($m->element . '.sys', JPATH_ADMINISTRATOR);
$lang->load($m->element, JPATH_ADMINISTRATOR);
// Also try component-local language path (Joomla 5/6 pattern)
$compLangPath = JPATH_ADMINISTRATOR . '/components/' . $m->element;
if (is_dir($compLangPath . '/language'))
{
$lang->load($m->element . '.sys', $compLangPath);
$lang->load($m->element, $compLangPath);
}
$loadedLangs[$m->element] = true;
}
}
// Group: level 1 = component parent, level 2 = children
foreach ($menuItems as $m)
{
if ((int) $m->level === 1)
{
$icon = $iconOverrides[$m->element]
?? str_replace('class:', 'icon-', $m->img ?: 'class:puzzle-piece');
$mokoComponents[$m->element] = [
'id' => $m->id,
'title' => Text::_($m->title),
'link' => $m->link,
'icon' => str_replace('class:', 'icon-', $m->img ?: 'class:puzzle-piece'),
'icon' => $icon,
'element' => $m->element,
'children' => [],
];
}
elseif ((int) $m->level === 2 && isset($mokoComponents[$m->element]))
{
$childIcon = str_replace('class:', 'icon-', $m->img ?: '');
if ($childIcon === '' || $childIcon === 'icon-cog' || $childIcon === 'icon-cogs')
{
$parsed = [];
parse_str(parse_url($m->link, PHP_URL_QUERY) ?? '', $parsed);
$viewKey = strtolower($parsed['view'] ?? '');
$childIcon = $childIconMap[$viewKey] ?? '';
}
$mokoComponents[$m->element]['children'][] = [
'title' => Text::_($m->title),
'link' => $m->link,
'icon' => str_replace('class:', 'icon-', $m->img ?: 'class:cog'),
'icon' => $childIcon ?: 'icon-angle-right',
];
}
}
}
catch (\Throwable $e)
{
// Silent — menu works without auto-discovered components
}
catch (\Throwable $e) {}
// Override com_mokosuiteclient children with static views
if (isset($mokoComponents['com_mokosuiteclient']))
@@ -115,7 +182,6 @@ if (isset($mokoComponents['com_mokosuiteclient']))
}
else
{
// com_mokosuiteclient not in admin menu — add it manually
$mokoComponents['com_mokosuiteclient'] = [
'id' => 0,
'title' => 'MokoSuite',
@@ -133,9 +199,6 @@ $rest = [];
foreach ($mokoComponents as $key => $comp)
{
// Shorten display titles:
// MokoSuiteClient → MokoSuite, MokoSuiteHQ → MokoHQ
// Everything else: MokoSuiteBackup → Backup, MokoSuiteOpenGraph → OpenGraph
if ($key === 'com_mokosuiteclient')
{
$comp['title'] = 'MokoSuite';
@@ -149,35 +212,29 @@ foreach ($mokoComponents as $key => $comp)
$comp['title'] = preg_replace('/^MokoSuite\s*/i', '', $comp['title']);
}
if ($key === 'com_mokosuitehq')
{
$hq = $comp;
}
elseif ($key === 'com_mokosuiteclient')
{
$client = $comp;
}
else
{
$rest[$key] = $comp;
}
if ($key === 'com_mokosuitehq') { $hq = $comp; }
elseif ($key === 'com_mokosuiteclient') { $client = $comp; }
else { $rest[$key] = $comp; }
}
usort($rest, fn($a, $b) => strcasecmp($a['title'], $b['title']));
$sorted = [];
if ($hq !== null)
if ($hq !== null) $sorted[] = $hq;
if ($client !== null) $sorted[] = $client;
foreach ($rest as $comp) $sorted[] = $comp;
// Is ANY Moko component active?
$anyActive = false;
foreach ($sorted as $comp)
{
$sorted[] = $hq;
}
if ($client !== null)
{
$sorted[] = $client;
}
foreach ($rest as $comp)
{
$sorted[] = $comp;
$p = [];
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $p);
if (($p['option'] ?? '') === $currentOption) { $anyActive = true; break; }
}
if ($currentOption === 'com_plugins') $anyActive = true;
$iconStyle = 'display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;';
?>
<style>
@@ -185,58 +242,64 @@ foreach ($rest as $comp)
</style>
<ul class="nav flex-column main-nav">
<?php foreach ($sorted as $comp): ?>
<?php
$compParsed = [];
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $compParsed);
$compOption = $compParsed['option'] ?? '';
$compActive = ($compOption === $currentOption);
// For com_mokosuiteclient static children, also check the plugins filter link
if (!$compActive && $comp['element'] === 'com_mokosuiteclient' && $currentOption === 'com_plugins')
{
$compActive = true;
}
$hasChildren = !empty($comp['children']);
$liClass = 'item mokosuiteclient-ext-item' . ($hasChildren ? ' parent item-level-1' : '') . ($compActive ? ' mm-active' : '');
$aClass = ($hasChildren ? 'has-arrow' : 'no-dropdown') . ($compActive ? ' mm-active' : '');
$childCollapse = 'collapse-level-1 mm-collapse' . ($compActive ? ' mm-show' : '');
?>
<li class="<?php echo $liClass; ?>">
<a class="<?php echo $aClass; ?>" href="<?php echo $hasChildren ? '#' : Route::_($comp['link']); ?>"<?php echo ($compActive && !$hasChildren) ? ' aria-current="page"' : ''; ?>>
<span class="<?php echo $comp['icon']; ?>" aria-hidden="true" style="display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;"></span>
<span class="sidebar-item-title"><?php echo $comp['title']; ?></span>
<li class="item parent item-level-1 mokosuiteclient-ext-item<?php echo $anyActive ? ' mm-active' : ''; ?>">
<a class="has-arrow<?php echo $anyActive ? ' mm-active' : ''; ?>" href="#">
<span class="icon-shield-alt" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
<span class="sidebar-item-title">MokoSuite</span>
</a>
<?php if ($hasChildren): ?>
<ul class="<?php echo $childCollapse; ?>" style="padding-inline-start:0.5rem;">
<?php foreach ($comp['children'] as $child): ?>
<ul class="collapse-level-1 mm-collapse<?php echo $anyActive ? ' mm-show' : ''; ?>" style="padding-inline-start:0.5rem;">
<?php foreach ($sorted as $comp): ?>
<?php
$childParsed = [];
parse_str(parse_url($child['link'], PHP_URL_QUERY) ?? '', $childParsed);
$childOption = $childParsed['option'] ?? '';
$childView = $childParsed['view'] ?? '';
$childActive = false;
if ($childOption === $currentOption)
$compParsed = [];
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $compParsed);
$compOption = $compParsed['option'] ?? '';
$compActive = ($compOption === $currentOption);
if (!$compActive && $comp['element'] === 'com_mokosuiteclient' && $currentOption === 'com_plugins')
{
$childActive = empty($childView)
? ($currentView === '' || $currentView === 'dashboard')
: ($currentView === $childView);
$compActive = true;
}
$childLiClass = 'item mokosuiteclient-ext-child' . ($childActive ? ' mm-active' : '');
$childAClass = 'no-dropdown' . ($childActive ? ' mm-active' : '');
$hasChildren = !empty($comp['children']);
?>
<li class="<?php echo $childLiClass; ?>">
<a class="<?php echo $childAClass; ?>" href="<?php echo Route::_($child['link']); ?>"<?php echo $childActive ? ' aria-current="page"' : ''; ?>>
<span class="<?php echo $child['icon']; ?>" aria-hidden="true" style="display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;"></span>
<span class="sidebar-item-title"><?php echo $child['title']; ?></span>
<?php if ($hasChildren): ?>
<li class="item parent item-level-2 mokosuiteclient-ext-item<?php echo $compActive ? ' mm-active' : ''; ?>">
<a class="has-arrow<?php echo $compActive ? ' mm-active' : ''; ?>" href="#">
<span class="<?php echo htmlspecialchars($comp['icon'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
<span class="sidebar-item-title"><?php echo htmlspecialchars($comp['title'], ENT_QUOTES, 'UTF-8'); ?></span>
</a>
<ul class="collapse-level-2 mm-collapse<?php echo $compActive ? ' mm-show' : ''; ?>" style="padding-inline-start:0.5rem;">
<?php foreach ($comp['children'] as $child): ?>
<?php
$childParsed = [];
parse_str(parse_url($child['link'], PHP_URL_QUERY) ?? '', $childParsed);
$childOption = $childParsed['option'] ?? '';
$childView = $childParsed['view'] ?? '';
$childActive = false;
if ($childOption === $currentOption)
{
$childActive = empty($childView)
? ($currentView === '' || $currentView === 'dashboard')
: ($currentView === $childView);
}
?>
<li class="item mokosuiteclient-ext-child<?php echo $childActive ? ' mm-active' : ''; ?>">
<a class="no-dropdown<?php echo $childActive ? ' mm-active' : ''; ?>" href="<?php echo Route::_($child['link']); ?>"<?php echo $childActive ? ' aria-current="page"' : ''; ?>>
<span class="<?php echo htmlspecialchars($child['icon'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
<span class="sidebar-item-title"><?php echo htmlspecialchars($child['title'], ENT_QUOTES, 'UTF-8'); ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</li>
<?php else: ?>
<li class="item mokosuiteclient-ext-item<?php echo $compActive ? ' mm-active' : ''; ?>">
<a class="no-dropdown<?php echo $compActive ? ' mm-active' : ''; ?>" href="<?php echo Route::_($comp['link']); ?>"<?php echo $compActive ? ' aria-current="page"' : ''; ?>>
<span class="<?php echo htmlspecialchars($comp['icon'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true" style="<?php echo $iconStyle; ?>"></span>
<span class="sidebar-item-title"><?php echo htmlspecialchars($comp['title'], ENT_QUOTES, 'UTF-8'); ?></span>
</a>
</li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.48.16
* VERSION: 02.50.00
* PATH: /src/Extension/MokoSuiteClient.php
* NOTE: Core system plugin for MokoSuiteClient admin tools suite
*/
@@ -384,14 +384,20 @@ class MokoSuiteClient extends CMSPlugin implements BootableExtensionInterface
document.querySelectorAll('a[href*=\"help.joomla.org\"], a[href*=\"docs.joomla.org\"]').forEach(function(link) {
link.href = url;
link.target = '_blank';
link.rel = 'noopener noreferrer';
var extIcon = link.querySelector('.icon-external-link-alt, .icon-external-link, .fa-external-link-alt, .fa-up-right-from-square');
if (extIcon) extIcon.remove();
});
document.querySelectorAll('a[href*=\"dashboard=help\"]').forEach(function(link) {
link.href = url;
link.target = '_blank';
link.rel = 'noopener noreferrer';
var extIcon = link.querySelector('.icon-external-link-alt, .icon-external-link, .fa-external-link-alt, .fa-up-right-from-square');
if (extIcon) extIcon.remove();
});
});
");
$doc->addStyleDeclaration('a[href=\"' . $supportUrl . '\"] .icon-external-link-alt, a[href=\"' . $supportUrl . '\"] .fa-up-right-from-square { display: none !important; }');
}
/**
@@ -8,7 +8,7 @@
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient
* VERSION: 02.48.16
* VERSION: 02.50.00
* PATH: /src/Field/ArticlesField.php
* BRIEF: List field that populates with published Joomla articles
*/
@@ -8,7 +8,7 @@
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient
* VERSION: 02.48.16
* VERSION: 02.50.00
* PATH: /src/Field/CopyableTokenField.php
* BRIEF: Read-only token field with a copy-to-clipboard button
*/
@@ -30,7 +30,7 @@
<license>GNU General Public License version 3 or later; see LICENSE.md</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>MokoSuiteClient core system plugin — coordinates feature plugins, heartbeat, health checks, and admin customizations.</description>
<namespace path=".">Moko\Plugin\System\MokoSuiteClient</namespace>
<scriptfile>script.php</scriptfile>
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.48.16
* VERSION: 02.50.00
* PATH: /src/script.php
* BRIEF: Installation script for MokoSuiteClient plugin
* NOTE: Handles installation, update, and uninstallation tasks including language override deployment
@@ -22,7 +22,7 @@
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoSuiteClient
* REPO: https://github.com/mokoconsulting-tech/mokosuiteclient
* VERSION: 02.48.16
* VERSION: 02.50.00
* PATH: /src/services/provider.php
* BRIEF: Service provider for dependency injection in Joomla 5.x
* NOTE: Registers the plugin with Joomla's DI container
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientBackup</namespace>
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_DBIP_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientDBIP</namespace>
@@ -23,6 +23,7 @@ class DBIP extends CMSPlugin implements SubscriberInterface
{
return [
'onAfterInitialise' => 'onAfterInitialise',
'onAfterRender' => 'onAfterRender',
];
}
@@ -80,4 +81,92 @@ class DBIP extends CMSPlugin implements SubscriberInterface
DBIPHelper::downloadCityDb($url);
}
/**
* Scan rendered admin HTML for IP addresses and enrich with geo flags + tooltips.
*/
public function onAfterRender(): void
{
$app = $this->getApplication();
if (!$app->isClient('administrator'))
{
return;
}
if ($app->getDocument()->getType() !== 'html')
{
return;
}
$body = $app->getBody();
if (empty($body))
{
return;
}
$ipv4 = '(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)';
$ipv6 = '(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))?';
$pattern = '#<code(?:\s[^>]*)?>(' . $ipv4 . '|' . $ipv6 . ')</code>#';
$cache = [];
$newBody = preg_replace_callback($pattern, function ($m) use (&$cache) {
$fullMatch = $m[0];
$ip = $m[1];
// Skip private/loopback
if (filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE) === false)
{
return $fullMatch;
}
// Already enriched (has a title attribute)
if (strpos($fullMatch, 'title=') !== false)
{
return $fullMatch;
}
if (!isset($cache[$ip]))
{
$cache[$ip] = DBIPHelper::lookup($ip);
}
$geo = $cache[$ip];
if ($geo === null || empty($geo['country_code']))
{
return $fullMatch;
}
$cc = strtoupper($geo['country_code']);
$flag = self::countryFlag($cc);
$parts = array_filter([$geo['city'], $geo['region'], $geo['country_name']]);
$tooltip = htmlspecialchars(implode(', ', $parts), \ENT_QUOTES, 'UTF-8');
$escaped = htmlspecialchars($ip, \ENT_QUOTES, 'UTF-8');
return $flag . ' <code title="' . $tooltip . '" style="cursor:help;">' . $escaped . '</code>';
}, $body);
if ($newBody !== null && $newBody !== $body)
{
$app->setBody($newBody);
}
}
private static function countryFlag(string $cc): string
{
if (\strlen($cc) !== 2)
{
return '';
}
$cc = strtoupper($cc);
$first = mb_chr(0x1F1E6 + \ord($cc[0]) - \ord('A'));
$second = mb_chr(0x1F1E6 + \ord($cc[1]) - \ord('A'));
return $first . $second;
}
}
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_DEVTOOLS_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientDevTools</namespace>
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_FIREWALL_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientFirewall</namespace>
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_LICENSE_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientLicense</namespace>
<files><folder>src</folder><folder>services</folder><folder>language</folder></files>
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_OFFLINE_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientOffline</namespace>
@@ -8,7 +8,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>PLG_SYSTEM_MOKOSUITECLIENT_TENANT_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteClientTenant</namespace>
@@ -12,7 +12,7 @@
<license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>PLG_TASK_MOKOSUITECLIENTDEMO_DESC</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientDemo</namespace>
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/DemoResetService.php
* VERSION: 02.48.16
* VERSION: 02.50.00
* BRIEF: Content-only snapshot/restore for demo site reset
*/
@@ -12,7 +12,7 @@
<license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>PLG_TASK_MOKOSUITECLIENTSYNC_DESC</description>
<namespace path="src">Moko\Plugin\Task\MokoSuiteClientSync</namespace>
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncReceiver.php
* VERSION: 02.48.16
* VERSION: 02.50.00
* BRIEF: Receiver-side content sync applies incoming payload to local DB
*/
@@ -10,7 +10,7 @@
* INGROUP: MokoSuiteClient
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteClient
* PATH: /src/packages/plg_system_mokosuiteclient/Service/ContentSyncService.php
* VERSION: 02.48.16
* VERSION: 02.50.00
* BRIEF: Sender-side content sync builds payload and pushes to remote sites
*/
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.48.16</version>
<version>02.50.00</version>
<description>Joomla Web Services API routes for MokoSuiteClient site management — health checks, cache, updates, backups, and site info.</description>
<namespace path="src">Moko\Plugin\WebServices\MokoSuiteClient</namespace>
<files>
+2 -2
View File
@@ -2,14 +2,14 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuiteClient</name>
<packagename>mokosuiteclient</packagename>
<version>02.48.16</version>
<version>02.50.00</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GNU General Public License version 3 or later; see LICENSE</license>
<description>MokoSuiteClient site management suite admin dashboard, security firewall, tenant restrictions, health monitoring, developer tools, and REST API.</description>
<description>MokoSuiteClient site management suite: admin dashboard, security firewall, tenant restrictions, health monitoring, developer tools, and REST API.</description>
<dlid prefix="dlid=" suffix=""/>
<blockChildUninstall>true</blockChildUninstall>
<scriptfile>script.php</scriptfile>