Compare commits

...

91 Commits

Author SHA1 Message Date
gitea-actions[bot] a038e43a3c chore(release): build 01.07.00 [skip ci] 2026-06-29 16:27:35 +00:00
jmiller b93ce7b872 Release: promote dev → main — v01.06.x cycle (dashboard, edit UI, ACL, security, forward-compat) 2026-06-29 16:27:20 +00:00
gitea-actions[bot] 320b842c6e chore(version): pre-release bump to 01.06.13-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 20s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 3m3s
2026-06-29 16:26:31 +00:00
gitea-actions[bot] 47f073539a chore(version): auto-bump patch 01.06.12-dev [skip ci] 2026-06-29 16:26:22 +00:00
jmiller 36c37c8e67 Merge origin/main into dev — resync 01.06.00 release chores before promotion
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 11s
2026-06-29 11:25:28 -05:00
gitea-actions[bot] e7d0395be0 chore(version): pre-release bump to 01.06.11-dev [skip ci] 2026-06-29 16:01:33 +00:00
jmiller 2e45d7ea5a Merge pull request 'docs: CHANGELOG + README for the current development cycle' (#114) from docs/session-updates into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
2026-06-29 16:01:13 +00:00
jmiller 0a5e2b94e2 docs: update CHANGELOG and README for this development cycle
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 11s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Universal: PR Check / Secret Scan (pull_request) Successful in 20s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 1m1s
Generic: Project CI / Tests (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
- CHANGELOG: fix the mangled header (duplicate empty [01.05.00], misplaced
  intro) and add a full [Unreleased] section covering the dashboard, edit UI,
  CSV import UI, access.xml/config.xml, AI/sitemap hardening, forward-compat,
  dead-code removal, and the new unit tests
- README: add Coverage dashboard / Manual tag editor / Component permissions to
  Admin Tools; drop the removed ImageGenerator overlay feature; note sitemap
  access-level filtering; add a Joomla 6.0+ / PHP 8.2+ Requirements line
2026-06-29 11:00:24 -05:00
gitea-actions[bot] 1dec76ff0c chore(version): pre-release bump to 01.06.10-dev [skip ci] 2026-06-29 15:49:30 +00:00
jmiller 60c243a733 Merge pull request 'feat: OG coverage dashboard as default admin view (#94)' (#112) from feat/dashboard-94 into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
2026-06-29 15:49:11 +00:00
jmiller 696e369ec1 feat: OG coverage dashboard as default admin view (#94)
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: PR Check / Secret Scan (pull_request) Successful in 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 17s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 48s
Generic: Project CI / Tests (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
- New DashboardModel (BaseDatabaseModel) with getStats(), getCoverageByType(),
  getMissingArticles() — coverage logic moved out of the list template
- New View/Dashboard/HtmlView + tmpl/dashboard/default.php: SVG donut coverage
  gauge, field-gap badges, per-content_type breakdown table, and a list of
  published articles missing OG tags (linking to the article editor) with a
  Batch Generate shortcut
- DisplayController default_view -> dashboard; Dashboard + Tags submenu entries
- Removed the inline coverage.php include from the tags list (it ran 6 uncached
  COUNT queries on every list load); that logic now lives in DashboardModel
- Declared tmpl/dashboard in the manifest; added language strings (en-GB, en-US)
2026-06-29 10:48:33 -05:00
gitea-actions[bot] 93b28a851e chore(version): pre-release bump to 01.06.09-dev [skip ci] 2026-06-29 15:29:14 +00:00
jmiller cb28cb12cd Merge pull request 'fix: features & quality — access.xml/config.xml, CSV import UI, dead-code/leak, packaging (#95 #103 #104 #107)' (#111) from fix/features-quality-batch into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
2026-06-29 15:26:40 +00:00
jmiller d8376d6cdf fix: features & quality batch (#95, #103, #104, #107)
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 16s
Generic: Project CI / Lint & Validate (pull_request) Successful in 48s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 52s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 5s
Generic: Project CI / Tests (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
#95 — ACL + Options:
- Add access.xml (core actions + mokoog.batch / mokoog.import custom actions)
- Add config.xml (Permissions tab + note pointing settings to the system plugin)
- Declare both in the manifest; Batch/ImportExport controllers now check the
  custom actions with a fallback to the prior core checks (no lockout)

#103 — CSV import is now reachable:
- Add an Import toolbar button that toggles a multipart file-upload form
  (jform[csv_file]) posting to importexport.import with a CSRF token

#104 — Dead code + disk leak:
- Delete unused ImageGenerator class and JsonLdBuilder::buildOrganization()
- Add ImageHelper::pruneOldFiles() (deletes generated images older than 30d)
  and call it on content save so the generated-image cache is bounded

#107 — Packaging:
- Declare language/en-US in the component manifest (was never installed)
- Remove undeclared empty stub dirs src/Field, src/Service
2026-06-29 10:25:59 -05:00
gitea-actions[bot] 543bd2b464 chore(version): pre-release bump to 01.06.08-dev [skip ci] 2026-06-29 15:23:05 +00:00
gitea-actions[bot] 32bb72d12d chore(version): auto-bump patch 01.06.07-dev [skip ci] 2026-06-29 15:22:48 +00:00
jmiller 4f92b4e508 chore: migrate update server URLs to MokoGitea
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s
2026-06-29 15:21:55 +00:00
gitea-actions[bot] 53bf4a3187 chore(version): pre-release bump to 01.06.06-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
2026-06-29 15:14:04 +00:00
jmiller 87267d8e80 chore: migrate update server URLs to MokoGitea
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: PR Check / Secret Scan (pull_request) Successful in 11s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 15s
Generic: Project CI / Lint & Validate (pull_request) Successful in 18s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 21s
Generic: Project CI / Tests (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
2026-06-29 15:12:41 +00:00
gitea-actions[bot] 11e50c54bb chore(version): pre-release bump to 01.06.05-dev [skip ci] 2026-06-29 14:54:30 +00:00
jmiller 377ae2d39e Merge pull request 'fix: security & correctness batch — AI ACL, sitemap disclosure, API/CSV columns, forward-compat (#99 #100 #101 #102 #106)' (#109) from fix/security-correctness-batch into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
2026-06-29 14:53:41 +00:00
gitea-actions[bot] a60ba86b19 chore(version): pre-release bump to 01.06.04-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
2026-06-29 14:53:12 +00:00
jmiller 71a102028d fix: security & correctness batch (#99, #100, #101, #102, #106)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
#99 — AI AJAX endpoint hardening:
- require core.edit/core.create on com_content before generating (was
  reachable by any authenticated back-end user → paid-credit abuse)
- callAiApi: 20s timeout + HTTP status check (throw on non-200) instead of
  silently returning an empty string

#100 — Sitemap information disclosure + robustness:
- filter to public (guest) view levels so registered/special-access
  articles are never written into the public sitemap
- atomic write (temp file + rename) so concurrent saves can't expose a
  half-written sitemap.xml
- (throttling + SEF URLs remain follow-ups, noted on the issue)

#101 — Expose newer columns in CSV + API:
- og_video, event_data, recipe_data, custom_schema added to CSV export/import
  (appended, so existing CSVs still import) and to the REST API field whitelist
- import validates JSON fields as arrays/objects and og_video as http(s)
  (prevents re-introducing the #97 scalar-JSON-LD crash via import)

#102 — Forward-compat (complete):
- Factory::getLanguage() -> getApplication()->getLanguage() (4 sites)
- Joomla\CMS\Filesystem\File/Folder -> Joomla\Filesystem\* (ImageHelper, ImageGenerator)

#106 — partial: loadArticle() now caches null misses (array_key_exists),
getArticleDate() skips 0000-00-00 dates. Batch-JS halt deferred — the
offset=0 design re-fetches failed rows, so the created>0 guard prevents an
infinite loop; a safe fix needs cursor-based pagination in BatchController.
2026-06-29 09:52:51 -05:00
gitea-actions[bot] 8858c81f87 chore(version): pre-release bump to 01.06.03-dev [skip ci] 2026-06-29 14:41:27 +00:00
jmiller 5b1fb1584e Merge pull request 'fix: resolve release blockers #97 (scalar JSON-LD 500) and #98 (no single-tag edit UI)' (#108) from fix/release-blockers-97-98 into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
2026-06-29 14:41:12 +00:00
gitea-actions[bot] e6328a1e8d chore(version): pre-release bump to 01.06.02-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
2026-06-29 14:36:11 +00:00
jmiller 8582a3eac5 fix: resolve release blockers #97 (scalar JSON-LD 500) and #98 (no edit UI)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s
#97 — Fatal frontend 500 from scalar custom_schema:
- MokoOGContent::validateJson() now requires a JSON object/array (rejects
  scalars like 42/"x"/true that previously passed and were stored)
- MokoOG render path guards with is_array($decoded) so already-stored
  scalar payloads can no longer crash the public page

#98 — Missing single-tag create/edit admin UI:
- Add Controller/TagController (FormController), View/Tag/HtmlView, tmpl/tag/edit.php
- Link OG title in the list to the editor; add New/Edit toolbar buttons
- Declare tmpl/tag folder in the component manifest
- tag.xml: switch cross-package PLG_CONTENT_* labels to COM_MOKOOG_* keys,
  make content_type/content_id editable+required (enables New), add language field
- Add the new COM_MOKOOG_* strings to en-GB and en-US

Also fixes #77 while here: seo_title/meta_description form maxlength now
match the DB columns (70/200) instead of 255.
2026-06-29 09:35:36 -05:00
jmiller 37d3d2a5b3 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-28 20:07:41 +00:00
jmiller ce108475a5 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-28 19:53:34 +00:00
gitea-actions[bot] 979ac9823f chore: promote changelog [Unreleased] → [01.06.00] 2026-06-28 19:52:02 +00:00
gitea-actions[bot] 2fb7d10e39 chore(release): build 01.06.00 [skip ci] 2026-06-28 19:51:48 +00:00
gitea-actions[bot] 6d4284c6c9 chore(version): pre-release bump to 01.05.02-dev [skip ci] 2026-06-28 19:51:06 +00:00
jmiller 57333482e3 Merge PR #84: Joomla-7 forward-compatibility cleanup 2026-06-28 19:50:59 +00:00
gitea-actions[bot] 3ac54da149 chore(version): auto-bump patch 01.05.01-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 36s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Successful in 5m24s
2026-06-28 19:50:47 +00:00
jmiller 2af8a72ca3 Merge origin/main into dev — resync after 01.05.00 release
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: Auto Version Bump / Version Bump (push) Successful in 16s
Generic: Project CI / Lint & Validate (pull_request) Successful in 18s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 47s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 58s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 54s
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
2026-06-28 14:50:25 -05:00
gitea-actions[bot] 740fb4e1f6 chore(version): pre-release bump to 01.05.01-dev [skip ci] 2026-06-28 19:48:07 +00:00
gitea-actions[bot] 1413f62476 chore(version): auto-bump patch 01.04.18-dev [skip ci] 2026-06-28 19:47:54 +00:00
jmiller d6fb2816cf refactor: replace Joomla-7-deprecated APIs (forward compatibility)
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
Removes accessors deprecated in Joomla 5 and slated for removal in 7
(extension already runs on 6; this future-proofs for 7):
- Factory::getDbo() -> Factory::getContainer()->get(DatabaseInterface::class)
  across plugins, controllers, static helpers, and the install script
- Factory::getUser() -> Factory::getApplication()->getIdentity()
- Factory::getSession() -> Factory::getApplication()->getSession()
- jexit(Text::_('JINVALID_TOKEN')) -> throw new \RuntimeException(..., 403),
  consistent with the access-denied checks already in those controllers

Note: SQL update-version concern is already resolved — the release bumped
to 01.05.00, which matches the 01.05.00.sql update slot.
2026-06-28 14:47:34 -05:00
jmiller 464514bc37 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-28 19:39:07 +00:00
gitea-actions[bot] 2b0a412066 chore: promote changelog [Unreleased] → [01.05.00] 2026-06-28 19:35:27 +00:00
gitea-actions[bot] 5cf39a5a3a chore(release): build 01.05.00 [skip ci] 2026-06-28 19:35:19 +00:00
jmiller 1c8f4e6867 Merge PR #83: Release v2.0 — schema types, AI meta gen, sitemap, Joomla 6+, MokoSuite rename 2026-06-28 19:35:01 +00:00
gitea-actions[bot] 77cf557b71 chore(version): pre-release bump to 01.04.17-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 24s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 33m1s
2026-06-28 19:34:12 +00:00
gitea-actions[bot] ff2c1a0483 chore(version): auto-bump patch 01.04.16-dev [skip ci] 2026-06-28 19:33:59 +00:00
jmiller 7fb7e38762 refactor: rename MokoJoomOpenGraph -> MokoSuiteOpenGraph; require Joomla 6+
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 10s
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Generic: Project CI / Lint & Validate (pull_request) Successful in 16s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 47s
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
Product rename (display name / docs / comments / language strings only —
technical element names mokoog/com_mokoog/MokoOG namespace unchanged):
- Replace "MokoJoom" -> "MokoSuite" across 55 files
- Fixes the update-site license lookup in script.php, which matched the
  old "%MokoJoomOpenGraph%" name and would never find a "MokoSuite" site

Joomla 6 compatibility:
- script.php: minimumJoomla 4.0.0 -> 6.0.0, minimumPhp 8.1.0 -> 8.2.0,
  and actually enforce the Joomla floor in preflight() (was PHP-only)
- Add PKG_MOKOOG_JOOMLA_VERSION_ERROR language strings (en-GB, en-US)
- openapi.yaml + README state Joomla 6.0+ requirement
- Audit confirmed the codebase already uses only Joomla-6-supported APIs
2026-06-28 14:33:35 -05:00
gitea-actions[bot] cedf6808d2 chore(version): pre-release bump to 01.04.15-dev [skip ci] 2026-06-28 19:25:11 +00:00
gitea-actions[bot] 36ce686ae1 chore(version): auto-bump patch 01.04.14-dev [skip ci] 2026-06-28 19:24:54 +00:00
jmiller 5ea422d75e docs(api): require Joomla 6.0+ in OpenAPI metadata
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 12s
Generic: Project CI / Lint & Validate (pull_request) Successful in 48s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 59s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
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
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
2026-06-28 14:19:29 -05:00
jmiller e6e525080f Merge origin/main into dev — reconcile diverged trunks for v2.0 release
main carried template/governance syncs not in dev; dev carries the v2.0
feature work. Resolved per project convention:
- ours (dev): composer.json (main's was corrupted to 'mokojoomgallery'),
  all 5 manifests (01.04.13), README.md, CHANGELOG.md, all source
- theirs (main): GOVERNANCE.md, SECURITY.md, and workflow files (template syncs)
- kept dev's deliberate removal of deploy-manual.yml

This makes dev a superset of main so PR #83 (dev -> main) merges cleanly.
2026-06-28 14:15:17 -05:00
gitea-actions[bot] 42ffb4b46c chore(version): pre-release bump to 01.04.13-dev [skip ci]
Publish to Composer / Publish Package (release) Successful in 5s
2026-06-28 18:56:12 +00:00
gitea-actions[bot] 2907e64641 chore(version): auto-bump patch 01.04.12-dev [skip ci] 2026-06-28 18:56:01 +00:00
jmiller b77054b769 fix: harden input handling and output safety (#79)
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
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 4s
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 11s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Failing after 9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 13s
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
- canonical_url: sanitize via sanitizeUrl() (scheme allowlist) instead of
  bare trim() — closes stored-XSS via addHeadLink() on the public frontend
- AI endpoint: replace die('Invalid Token') with a clean event result,
  and strip_tags + truncate article_title to 200 chars before use
- SitemapBuilder: whitelist changefreq against the sitemap spec enum,
  intval() noindex IDs, strict in_array comparison
- MokoOG: log a WARNING when sitemap.xml write fails instead of ignoring it
2026-06-28 13:55:14 -05:00
Jonathan Miller 3fb5a87be9 fix: use mysqli driver in component manifest for Joomla 4/5/6
The install/uninstall/update SQL sections used driver="mysql" which
doesn't match the active mysqli driver, causing "SQL File not found"
on fresh installs and silently skipping schema updates on upgrades.
2026-06-28 13:55:12 -05:00
jmiller aa4e254f0b chore: sync pr-metadata-check.yml from Template-Joomla 2026-06-28 07:47:38 +00:00
jmiller 9147790214 chore: sync SECURITY.md from Template-Joomla 2026-06-28 07:46:12 +00:00
jmiller 3e51d8c439 chore: sync GOVERNANCE.md from Template-Joomla 2026-06-28 07:42:40 +00:00
jmiller 750f769a13 chore: sync CONTRIBUTING.md from Template-Joomla 2026-06-28 07:40:55 +00:00
jmiller 981464ee4e chore: sync CODE_OF_CONDUCT.md from Template-Joomla 2026-06-28 07:37:50 +00:00
jmiller 7afcc8e6b9 chore: sync composer.json from Template-Joomla 2026-06-28 07:35:50 +00:00
jmiller e47fdf8722 chore: sync phpstan.neon from Template-Joomla 2026-06-28 07:34:31 +00:00
jmiller 872abec8bc chore: sync .editorconfig from Template-Joomla 2026-06-28 07:33:59 +00:00
jmiller 6cc08927e7 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-06-27 20:44:50 +00:00
jmiller ed715b5db8 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-06-27 05:32:30 +00:00
jmiller 5d02db24d5 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-27 00:49:26 +00:00
jmiller e6ade9033d chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-25 19:47:01 +00:00
jmiller 76845f78f2 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-25 19:47:00 +00:00
jmiller b68d3f6481 chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-06-25 19:46:59 +00:00
jmiller 3110d7eb75 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-06-25 17:11:55 +00:00
jmiller e285b8e770 chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-25 17:11:54 +00:00
jmiller 0997a875d6 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-25 17:11:54 +00:00
jmiller baf67e18e6 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-25 17:11:53 +00:00
jmiller cf6b1286b5 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-25 17:11:53 +00:00
jmiller c1f560704b chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-25 17:11:52 +00:00
jmiller 52edde00c9 chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-25 17:11:51 +00:00
jmiller 759af6b237 chore: sync cleanup.yml from Template-Generic [skip ci] 2026-06-25 17:11:51 +00:00
jmiller e0112d770a chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-25 17:11:50 +00:00
jmiller 5544878cf2 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-06-25 17:11:49 +00:00
jmiller bd551bffda chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-24 11:51:16 +00:00
jmiller 48eeb9631f chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-24 11:51:14 +00:00
jmiller 2e3331170f chore: remove security-audit.yml -- handled by MokoGitea 2026-06-23 18:27:30 +00:00
jmiller 4699686f26 chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-23 17:51:20 +00:00
jmiller a7fe881d84 chore: remove deprecated .mokogitea/workflows/composer-publish.yml [skip ci] 2026-06-23 17:37:50 +00:00
jmiller ab02de34f4 chore: remove deprecated .mokogitea/workflows/update-server.yml [skip ci] 2026-06-23 17:37:47 +00:00
jmiller 64d9a97db1 chore: remove deprecated .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-06-23 17:37:44 +00:00
jmiller 3ba1c3ead4 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-23 16:05:23 +00:00
jmiller 4c091805ee chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-23 16:05:22 +00:00
jmiller 0d4e7785a3 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-23 16:05:21 +00:00
jmiller 6f13a10a34 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-06-23 16:05:18 +00:00
gitea-actions[bot] 5f1e44e66b chore: promote changelog [Unreleased] → [01.04.00] 2026-06-23 16:04:28 +00:00
gitea-actions[bot] 646dd23e81 chore(release): build 01.04.00 [skip ci]
Publish to Composer / Publish Package (release) Successful in 27s
2026-06-23 16:04:22 +00:00
jmiller d4229fd450 Merge pull request 'feat: v1.3 — multi-platform social tags, editor UX, video support' (#82) from dev into main 2026-06-23 16:03:30 +00:00
118 changed files with 1723 additions and 975 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
# MokoJoomOpenGraph # MokoSuiteOpenGraph
Open Graph, Twitter Card, and social sharing meta tag management for Joomla. Per-article SEO with auto-generation fallback. Open Graph, Twitter Card, and social sharing meta tag management for Joomla. Per-article SEO with auto-generation fallback.
@@ -9,7 +9,7 @@ Open Graph, Twitter Card, and social sharing meta tag management for Joomla. Per
| **Package** | `pkg_mokoog` | | **Package** | `pkg_mokoog` |
| **Language** | PHP 8.1+ | | **Language** | PHP 8.1+ |
| **Branch** | develop on `dev`, merge to `main` (protected) | | **Branch** | develop on `dev`, merge to `main` (protected) |
| **Wiki** | [MokoJoomOpenGraph Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomOpenGraph/wiki) | | **Wiki** | [MokoSuiteOpenGraph Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteOpenGraph/wiki) |
## Commands ## Commands
+1 -1
View File
@@ -22,7 +22,7 @@ on:
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
permissions: permissions:
contents: write contents: write
+15 -12
View File
@@ -7,7 +7,7 @@
# INGROUP: mokocli.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template # PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00 # VERSION: 05.01.00
# BRIEF: Universal build & release detects platform from manifest.xml # BRIEF: Universal build & release detects platform from manifest.xml
# #
# +=======================================================================+ # +=======================================================================+
@@ -27,7 +27,7 @@ name: "Universal: Build & Release"
on: on:
pull_request: pull_request:
types: [opened, closed] types: [opened, synchronize, closed]
branches: branches:
- main - main
paths-ignore: paths-ignore:
@@ -52,7 +52,7 @@ on:
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
@@ -66,6 +66,7 @@ jobs:
runs-on: release runs-on: release
if: >- if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) || (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') (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps: steps:
@@ -74,6 +75,7 @@ jobs:
with: with:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1 fetch-depth: 1
submodules: recursive
- name: Setup mokocli tools - name: Setup mokocli tools
env: env:
@@ -101,7 +103,7 @@ jobs:
php ${MOKO_CLI}/branch_rename.php \ php ${MOKO_CLI}/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \ --from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \ --api-base "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}" --pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git - name: Checkout rc and configure git
@@ -120,7 +122,7 @@ jobs:
- name: Update RC release notes from CHANGELOG.md - name: Update RC release notes from CHANGELOG.md
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Extract [Unreleased] section from changelog # Extract [Unreleased] section from changelog
@@ -172,6 +174,7 @@ jobs:
with: with:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0 fetch-depth: 0
submodules: recursive
- name: Configure git for bot pushes - name: Configure git for bot pushes
run: | run: |
@@ -268,7 +271,7 @@ jobs:
!startsWith(steps.platform.outputs.platform, 'joomla') !startsWith(steps.platform.outputs.platform, 'joomla')
run: | run: |
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
SEMVER_TAG="v${VERSION}" SEMVER_TAG="v${VERSION}"
@@ -293,7 +296,7 @@ jobs:
- name: Update release notes and promote changelog - name: Update release notes and promote changelog
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Get the stable release info (version and ID) # Get the stable release info (version and ID)
@@ -362,7 +365,7 @@ jobs:
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_mirror.php \ php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \ --version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
@@ -391,7 +394,7 @@ jobs:
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc) # Delete rc branch (ephemeral — created by promote-rc)
@@ -415,7 +418,7 @@ jobs:
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}" BRANCH_NAME="version/${VERSION}"
@@ -436,7 +439,7 @@ jobs:
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \ php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true --branch dev --path . 2>&1 || true
@@ -462,5 +465,5 @@ jobs:
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi fi
+6
View File
@@ -13,6 +13,12 @@
name: "Generic: Project CI" name: "Generic: Project CI"
on: on:
pull_request:
branches:
- main
- dev
- dev/**
- rc/**
workflow_dispatch: workflow_dispatch:
permissions: permissions:
+68 -1
View File
@@ -1 +1,68 @@
IyBDb3B5cmlnaHQgKEMpIDIwMjYgTW9rbyBDb25zdWx0aW5nIDxoZWxsb0Btb2tvY29uc3VsdGluZy50ZWNoPgojCiMgU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEdQTC0zLjAtb3ItbGF0ZXIKIwojIEZJTEUgSU5GT1JNQVRJT04KIyBERUZHUk9VUDogR2l0ZWEuV29ya2Zsb3cKIyBJTkdST1VQOiBtb2tvY2xpLlVuaXZlcnNhbAojIFJFUE86IGh0dHBzOi8vZ2l0Lm1va29jb25zdWx0aW5nLnRlY2gvTW9rb0NvbnN1bHRpbmcvbW9rb2NsaQojIFBBVEg6IC8ubW9rb2dpdGVhL3dvcmtmbG93cy9jaS1pc3N1ZS1yZXBvcnRlci55bWwKIyBWRVJTSU9OOiAwMS4wMC4wMAojIEJSSUVGOiBSZXVzYWJsZSB3b3JrZmxvdyDigJQgY3JlYXRlcy91cGRhdGVzIGEgR2l0ZWEgaXNzdWUgd2hlbiBhIENJIGdhdGUgZmFpbHMuCiMgICAgICAgIENsb25lcyBNb2tvQ0xJIGFuZCBydW5zIGNsaS9jaV9pc3N1ZV9yZXBvcnRlci5zaC4KCm5hbWU6ICJVbml2ZXJzYWw6IENJIElzc3VlIFJlcG9ydGVyIgoKb246CiAgd29ya2Zsb3dfY2FsbDoKICAgIGlucHV0czoKICAgICAgZ2F0ZToKICAgICAgICBkZXNjcmlwdGlvbjogIkNJIGdhdGUgbmFtZSAoZS5nLiBQUiBWYWxpZGF0aW9uLCBSZXBvc2l0b3J5IEhlYWx0aCkiCiAgICAgICAgcmVxdWlyZWQ6IHRydWUKICAgICAgICB0eXBlOiBzdHJpbmcKICAgICAgZGV0YWlsczoKICAgICAgICBkZXNjcmlwdGlvbjogIkh1bWFuLXJlYWRhYmxlIGZhaWx1cmUgZGVzY3JpcHRpb24iCiAgICAgICAgcmVxdWlyZWQ6IHRydWUKICAgICAgICB0eXBlOiBzdHJpbmcKICAgICAgc2V2ZXJpdHk6CiAgICAgICAgZGVzY3JpcHRpb246ICJlcnJvciBvciB3YXJuaW5nIgogICAgICAgIHJlcXVpcmVkOiBmYWxzZQogICAgICAgIHR5cGU6IHN0cmluZwogICAgICAgIGRlZmF1bHQ6ICJlcnJvciIKICAgICAgd29ya2Zsb3c6CiAgICAgICAgZGVzY3JpcHRpb246ICJXb3JrZmxvdyBuYW1lIGZvciB0aGUgaXNzdWUgdGl0bGUiCiAgICAgICAgcmVxdWlyZWQ6IGZhbHNlCiAgICAgICAgdHlwZTogc3RyaW5nCiAgICAgICAgZGVmYXVsdDogIiIKICAgIHNlY3JldHM6CiAgICAgIE1PS09HSVRFQV9UT0tFTjoKICAgICAgICByZXF1aXJlZDogdHJ1ZQoKZW52OgogIEZPUkNFX0pBVkFTQ1JJUFRfQUNUSU9OU19UT19OT0RFMjQ6IHRydWUKCmpvYnM6CiAgcmVwb3J0OgogICAgbmFtZTogIlJlcG9ydDogJHt7IGlucHV0cy5nYXRlIH19IgogICAgcnVucy1vbjogdWJ1bnR1LWxhdGVzdAoKICAgIHN0ZXBzOgogICAgICAtIG5hbWU6IENsb25lIE1va29DTEkKICAgICAgICBlbnY6CiAgICAgICAgICBNT0tPR0lURUFfVE9LRU46ICR7eyBzZWNyZXRzLk1PS09HSVRFQV9UT0tFTiB9fQogICAgICAgIHJ1bjogfAogICAgICAgICAgTU9LT0dJVEVBX1VSTD0iJHt7IHZhcnMuR0lURUFfVVJMIHx8ICdodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoJyB9fSIKICAgICAgICAgIGdpdCBjbG9uZSAtLWRlcHRoIDEgLS1maWx0ZXI9YmxvYjpub25lIC0tc3BhcnNlICIke01PS09HSVRFQV9VUkx9L01va29Db25zdWx0aW5nL01va29DTEkuZ2l0IiAvdG1wL21va29jbGkKICAgICAgICAgIGNkIC90bXAvbW9rb2NsaSAmJiBnaXQgc3BhcnNlLWNoZWNrb3V0IHNldCBjbGkvY2lfaXNzdWVfcmVwb3J0ZXIuc2gKCiAgICAgIC0gbmFtZTogUmVwb3J0IENJIGZhaWx1cmUKICAgICAgICBlbnY6CiAgICAgICAgICBNT0tPR0lURUFfVE9LRU46ICR7eyBzZWNyZXRzLk1PS09HSVRFQV9UT0tFTiB9fQogICAgICAgICAgTU9LT0dJVEVBX1VSTDogJHt7IHZhcnMuR0lURUFfVVJMIHx8ICdodHRwczovL2dpdC5tb2tvY29uc3VsdGluZy50ZWNoJyB9fQogICAgICAgIHJ1bjogfAogICAgICAgICAgY2htb2QgK3ggL3RtcC9tb2tvY2xpL2NsaS9jaV9pc3N1ZV9yZXBvcnRlci5zaAogICAgICAgICAgL3RtcC9tb2tvY2xpL2NsaS9jaV9pc3N1ZV9yZXBvcnRlci5zaCBcCiAgICAgICAgICAgIC0tZ2F0ZSAiJHt7IGlucHV0cy5nYXRlIH19IiBcCiAgICAgICAgICAgIC0tZGV0YWlscyAiJHt7IGlucHV0cy5kZXRhaWxzIH19IiBcCiAgICAgICAgICAgIC0tc2V2ZXJpdHkgIiR7eyBpbnB1dHMuc2V2ZXJpdHkgfX0iIFwKICAgICAgICAgICAgLS13b3JrZmxvdyAiJHt7IGlucHV0cy53b3JrZmxvdyB9fSIK # 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 }}"
+10 -10
View File
@@ -21,7 +21,7 @@ permissions:
contents: write contents: write
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs: jobs:
cleanup: cleanup:
@@ -33,17 +33,17 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Delete merged branches - name: Delete merged branches
env: env:
GA_TOKEN: ${{ secrets.GA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
echo "=== Merged Branch Cleanup ===" echo "=== Merged Branch Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API # List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name') "${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0 DELETED=0
@@ -56,7 +56,7 @@ jobs:
# Check if branch is merged into main # Check if branch is merged into main
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
echo " Deleting merged branch: ${BRANCH}" echo " Deleting merged branch: ${BRANCH}"
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/branches/${BRANCH}" 2>/dev/null || true "${API}/branches/${BRANCH}" 2>/dev/null || true
DELETED=$((DELETED + 1)) DELETED=$((DELETED + 1))
fi fi
@@ -66,20 +66,20 @@ jobs:
- name: Clean old workflow runs - name: Clean old workflow runs
env: env:
GA_TOKEN: ${{ secrets.GA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
echo "=== Workflow Run Cleanup ===" echo "=== Workflow Run Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ) CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs # Get old completed runs
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/actions/runs?status=completed&limit=50" | \ "${API}/actions/runs?status=completed&limit=50" | \
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null) jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
DELETED=0 DELETED=0
for RUN_ID in $RUNS; do for RUN_ID in $RUNS; do
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1)) DELETED=$((DELETED + 1))
done done
-76
View File
@@ -1,76 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
name: "Publish to Composer"
on:
push:
tags:
- 'v*'
- '[0-9]*.[0-9]*.[0-9]*'
release:
types: [published]
workflow_dispatch:
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs:
publish:
name: Publish Package
runs-on: ubuntu-latest
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip publish]')
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
- name: Install dependencies
run: composer install --no-dev --no-interaction --prefer-dist --quiet
- name: Determine version
id: version
run: |
VERSION=$(php -r "echo json_decode(file_get_contents('composer.json'))->version;")
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Package version: ${VERSION}"
# Gitea Composer Registry — auto-publishes from tags
# The tag push itself registers the package at:
# https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer
- name: Verify Gitea registry
run: |
echo "Gitea Composer registry auto-publishes from tags."
echo "Package available at: ${GITEA_URL}/api/packages/MokoConsulting/composer"
echo "Install: composer require mokoconsulting/mokocli"
# Packagist — notify of new version
- name: Notify Packagist
if: secrets.PACKAGIST_TOKEN != ''
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "Notifying Packagist of version ${VERSION}..."
curl -sf -X POST \
-H "Content-Type: application/json" \
-d '{"repository":{"url":"https://git.mokoconsulting.tech/MokoConsulting/mokocli"}}' \
"https://packagist.org/api/update-package?username=mokoconsulting&apiToken=${{ secrets.PACKAGIST_TOKEN }}" \
&& echo "Packagist notified" \
|| echo "::warning::Packagist notification failed (package may not be registered yet)"
- name: Summary
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "## Composer Package Published" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Registry | Status |" >> $GITHUB_STEP_SUMMARY
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Gitea | \`composer require mokoconsulting/mokocli:${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Packagist | \`composer require mokoconsulting/mokocli\` |" >> $GITHUB_STEP_SUMMARY
+5 -5
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation # INGROUP: mokocli.Automation
# VERSION: 01.04.11 # VERSION: 01.07.00
# BRIEF: Auto-create feature branch when an issue is opened # BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch" name: "Universal: Issue Branch"
@@ -19,7 +19,7 @@ permissions:
issues: write issues: write
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs: jobs:
create-branch: create-branch:
@@ -28,8 +28,8 @@ jobs:
steps: steps:
- name: Create branch and comment - name: Create branch and comment
run: | run: |
TOKEN="${{ secrets.GA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
ISSUE_NUM="${{ github.event.issue.number }}" ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}" ISSUE_TITLE="${{ github.event.issue.title }}"
@@ -58,7 +58,7 @@ jobs:
echo "Created branch: ${BRANCH}" echo "Created branch: ${BRANCH}"
# Comment on issue with branch link # Comment on issue with branch link
REPO_URL="${GITEA_URL}/${{ github.repository }}" REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}"
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`" BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
curl -sf -X POST \ curl -sf -X POST \
+10 -23
View File
@@ -496,39 +496,26 @@ jobs:
steps: steps:
- name: Trigger RC pre-release - name: Trigger RC pre-release
env: env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }} BRANCH: ${{ github.head_ref }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: | run: |
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" curl -s -X POST "${MOKOGITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
# ── Issue Reporter ────────────────────────────────────────────────────── # ── Issue Reporter ──────────────────────────────────────────────────────
report-issues: report-issues:
name: Report Issues name: Report Issues
runs-on: ubuntu-latest
needs: [branch-policy, validate] needs: [branch-policy, validate]
if: >- if: >-
always() && always() &&
needs.validate.result == 'failure' needs.validate.result == 'failure'
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
steps: with:
- name: Checkout gate: "PR Validation"
uses: actions/checkout@v4 workflow: "PR Check"
with: severity: error
sparse-checkout: automation/ci-issue-reporter.sh details: "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
sparse-checkout-cone-mode: false secrets: inherit
- name: "File issue for PR validation failure"
env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
chmod +x automation/ci-issue-reporter.sh
./automation/ci-issue-reporter.sh \
--gate "PR Validation" \
--workflow "PR Check" \
--severity error \
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
+4 -4
View File
@@ -20,7 +20,7 @@ permissions:
contents: read contents: read
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
@@ -55,14 +55,14 @@ jobs:
- name: Validate metadata against Joomla manifest - name: Validate metadata against Joomla manifest
env: env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
php ${MOKO_CLI}/joomla_metadata_validate.php \ php ${MOKO_CLI}/joomla_metadata_validate.php \
--path . \ --path . \
--token "${GITEA_TOKEN}" \ --token "${MOKOGITEA_TOKEN}" \
--org "${GITEA_ORG}" \ --org "${GITEA_ORG}" \
--repo "${GITEA_REPO}" \ --repo "${GITEA_REPO}" \
--api-base "${GITEA_URL}/api/v1" \ --api-base "${MOKOGITEA_URL}/api/v1" \
--ci --ci
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
+6 -1
View File
@@ -7,7 +7,7 @@
# INGROUP: mokocli.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template # PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00 # VERSION: 05.02.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release" name: "Universal: Pre-Release"
@@ -59,6 +59,11 @@ jobs:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.ref_name }} ref: ${{ github.ref_name }}
submodules: recursive
- name: Update submodules to main
run: |
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
- name: Setup mokocli tools - name: Setup mokocli tools
env: env:
+18 -13
View File
@@ -29,12 +29,20 @@ jobs:
steps: steps:
- name: Rename branch - name: Rename branch
env:
BRANCH: ${{ github.event.pull_request.head.ref }}
REPO: ${{ github.repository }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
BRANCH="${{ github.event.pull_request.head.ref }}" set -euo pipefail
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
fi
SUFFIX="${BRANCH#rc/}" SUFFIX="${BRANCH#rc/}"
DEV_BRANCH="dev/${SUFFIX}" DEV_BRANCH="dev/${SUFFIX}"
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches" API="${GITEA_URL}/api/v1/repos/${REPO}/branches"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Create dev/ branch from rc/ branch # Create dev/ branch from rc/ branch
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \ STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
@@ -42,25 +50,22 @@ jobs:
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \ -d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
"${API}" 2>/dev/null || true) "${API}" 2>/dev/null || true)
if [ "$STATUS" = "201" ]; then if [ "$STATUS" = "201" ]; then
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else else
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})" echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
exit 1
fi fi
# Delete rc/ branch # Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');") ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \ STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
"${API}/${ENCODED}" 2>/dev/null || true) "${API}/${ENCODED}" 2>/dev/null || true)
if [ "$STATUS" = "204" ]; then if [ "$STATUS" = "204" ]; then
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else else
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})" echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
fi fi
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
+25 -37
View File
@@ -77,7 +77,7 @@ jobs:
- name: Check actor permission (admin only) - name: Check actor permission (admin only)
id: perm id: perm
env: env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
ACTOR: ${{ github.actor }} ACTOR: ${{ github.actor }}
run: | run: |
@@ -671,42 +671,30 @@ jobs:
# ═══════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════
# Issue Reporter — file issues for failed gates # Issue Reporter — file issues for failed gates
# ═══════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════
report-issues: report-scripts:
name: "Report Issues" name: "Report: Scripts Governance"
runs-on: ubuntu-latest needs: [access_check, scripts_governance]
needs: [access_check, scripts_governance, repo_health]
if: >- if: >-
always() && always() &&
(needs.scripts_governance.result == 'failure' || needs.scripts_governance.result == 'failure'
needs.repo_health.result == 'failure') uses: ./.mokogitea/workflows/ci-issue-reporter.yml
with:
gate: "Scripts Governance"
workflow: "Repo Health"
severity: error
details: "Scripts directory policy violations detected. Review required and allowed directories."
secrets: inherit
steps: report-health:
- name: Checkout name: "Report: Repository Health"
uses: actions/checkout@v4 needs: [access_check, repo_health]
with: if: >-
sparse-checkout: automation/ci-issue-reporter.sh always() &&
sparse-checkout-cone-mode: false needs.repo_health.result == 'failure'
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
- name: "File issues for failed gates" with:
env: gate: "Repository Health"
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} workflow: "Repo Health"
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} severity: error
run: | details: "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
chmod +x automation/ci-issue-reporter.sh secrets: inherit
REPORTER="./automation/ci-issue-reporter.sh"
WF="Repo Health"
report_gate() {
local gate="$1" result="$2" details="$3"
if [ "$result" = "failure" ]; then
"$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error
fi
}
report_gate "Scripts Governance" \
"${{ needs.scripts_governance.result }}" \
"Scripts directory policy violations detected. Review required and allowed directories."
report_gate "Repository Health" \
"${{ needs.repo_health.result }}" \
"Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
-312
View File
@@ -1,312 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/update-server.yml
# VERSION: 05.00.00
# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
#
# Thin wrapper around moko-platform CLI tools.
# Builds packages, updates updates.xml, and optionally deploys via SFTP.
#
# Joomla filters update entries by the user's "Minimum Stability" setting.
name: "Update Server"
on:
push:
branches:
- 'dev'
- 'dev/**'
- 'alpha/**'
- 'beta/**'
- 'rc/**'
paths:
- 'src/**'
- 'htdocs/**'
pull_request:
types: [closed]
branches:
- 'dev'
- 'dev/**'
- 'alpha/**'
- 'beta/**'
- 'rc/**'
paths:
- 'src/**'
- 'htdocs/**'
workflow_dispatch:
inputs:
stability:
description: 'Stability tag'
required: true
default: 'development'
type: choice
options:
- development
- alpha
- beta
- rc
- stable
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions:
contents: write
jobs:
update-xml:
name: Update Server
runs-on: release
if: >-
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.MOKOGITEA_TOKEN }}"}}}'
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
# Always fetch latest CLI tools — never use stale cache from previous runs
rm -rf /tmp/moko-platform
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 || true
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
- name: Detect platform
id: platform
run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve stability and bump version
id: meta
run: |
BRANCH="${{ github.ref_name }}"
# Configure git for bot pushes
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
# Auto-bump patch version
php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0")
# Strip any existing suffix before applying stability
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
# Determine stability from branch or manual input
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
STABILITY="${{ inputs.stability }}"
elif [[ "$BRANCH" == rc/* ]]; then
STABILITY="rc"
elif [[ "$BRANCH" == beta/* ]]; then
STABILITY="beta"
elif [[ "$BRANCH" == alpha/* ]]; then
STABILITY="alpha"
else
STABILITY="development"
fi
# Version suffix per stability stream
case "$STABILITY" in
development) SUFFIX="-dev"; TAG="development" ;;
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
beta) SUFFIX="-beta"; TAG="beta" ;;
rc) SUFFIX="-rc"; TAG="release-candidate" ;;
*) SUFFIX=""; TAG="stable" ;;
esac
# Propagate version with stability suffix to all manifest files
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "$BRANCH" --stability "$STABILITY" 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Re-read version (now includes suffix from version_set_platform)
if [ -n "$SUFFIX" ]; then
VERSION="${VERSION}${SUFFIX}"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "display_version=${VERSION}" >> "$GITHUB_OUTPUT"
# Commit version bump if changed
git add -A
git diff --cached --quiet || {
git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
git push
}
- name: Create release and upload package
id: package
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Create or update Gitea release
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
# Build package and upload
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
- name: Update updates.xml
if: steps.platform.outputs.platform == 'joomla'
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
if [ ! -f "updates.xml" ]; then
echo "No updates.xml — skipping"
exit 0
fi
SHA_FLAG=""
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
php ${MOKO_CLI}/updates_xml_build.php \
--path . --version "${VERSION}" --stability "${STABILITY}" \
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
${SHA_FLAG}
# Commit and push updates.xml
git add updates.xml
git diff --cached --quiet || {
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
git push
}
- name: Sync updates.xml to main
if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
FILE_SHA=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
python3 -c "
import base64, json, urllib.request, sys
with open('updates.xml', 'rb') as f:
content = base64.b64encode(f.read()).decode()
payload = json.dumps({
'content': content,
'sha': '${FILE_SHA}',
'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]',
'branch': 'main'
}).encode()
req = urllib.request.Request(
'${API_BASE}/contents/updates.xml',
data=payload, method='PUT',
headers={
'Authorization': 'token ${GITEA_TOKEN}',
'Content-Type': 'application/json'
})
try:
urllib.request.urlopen(req)
print('updates.xml synced to main')
except Exception as e:
print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
"
fi
- name: SFTP deploy to dev server
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
env:
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
run: |
# Permission check: admin or maintain role required
ACTOR="${{ github.actor }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
case "$PERMISSION" in
admin|maintain|write) ;;
*)
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
exit 0
;;
esac
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && exit 0
PORT="${DEV_PORT:-22}"
REMOTE="${DEV_PATH%/}"
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
if [ -n "$DEV_KEY" ]; then
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
else
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
fi
PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
- name: Summary
if: always()
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
DISPLAY="${{ steps.meta.outputs.display_version }}"
echo "## Update Server" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
File diff suppressed because one or more lines are too long
+12 -4
View File
@@ -13,6 +13,7 @@
name: "Universal: Workflow Sync Trigger" name: "Universal: Workflow Sync Trigger"
on: on:
workflow_dispatch:
pull_request: pull_request:
types: [closed] types: [closed]
branches: branches:
@@ -26,8 +27,9 @@ jobs:
name: Sync workflows to live repos name: Sync workflows to live repos
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >- if: >-
github.event.pull_request.merged == true && github.event_name == 'workflow_dispatch' ||
!contains(github.event.pull_request.title, '[skip sync]') (github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]'))
steps: steps:
- name: Determine platform from repo name - name: Determine platform from repo name
@@ -49,8 +51,14 @@ jobs:
env: env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}" MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
- name: Install PHP
run: |
if ! command -v php &> /dev/null; then
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
fi
- name: Install dependencies - name: Install dependencies
run: | run: |
+37 -2
View File
@@ -1,12 +1,46 @@
# Changelog # Changelog
<!-- VERSION: 01.04.11 --> ## [Unreleased]
All notable changes to MokoSuiteOpenGraph will be documented in this file. All notable changes to MokoSuiteOpenGraph will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased] <!-- VERSION: 01.07.00 -->
## [01.07.00] --- 2026-06-29
### Added
- OG coverage **dashboard** as the default admin view — SVG donut gauge, coverage by content type, and a list of articles missing OG tags with a batch-generate shortcut (#94)
- Single OG tag **create/edit screen** in the admin (the tag manager was previously read-only) (#98)
- **CSV import** button and upload form in the tag manager (#103)
- Component **Options** screen with a Permissions tab, plus `access.xml` ACL actions `mokoog.batch` and `mokoog.import` (#95)
- `og_video`, `event_data`, `recipe_data`, and `custom_schema` are now included in CSV import/export and the REST API (#101)
- Unit tests for `JsonLdBuilder::buildLocalBusiness()` and `toScriptTag()` (#33)
### Changed
- **Require Joomla 6.0+ and PHP 8.2+** (enforced at install)
- Renamed the product from *MokoJoomOpenGraph* to **MokoSuiteOpenGraph**
- Forward-compatibility for Joomla 7: replaced deprecated `Factory::getDbo/getUser/getSession/getLanguage`, `Joomla\CMS\Filesystem\File/Folder`, and `jexit()` (#102)
- Aligned OG/SEO form `maxlength` values with the database column limits (#77)
- Moved coverage metrics out of the tag list into a dedicated model (no longer runs uncached `COUNT` queries on every list load)
### Fixed
- Fatal frontend error (HTTP 500) when a non-object value was saved into the custom JSON-LD field — values are now validated as objects/arrays on save and guarded on render (#97)
- Stored XSS via the canonical URL field — now restricted to `http`/`https` (#79)
- Use the `mysqli` driver in the component manifest so install/upgrade SQL actually runs on Joomla 4/5/6
- `loadArticle()` now caches negative lookups; zero dates are no longer emitted as `article:published_time`/`article:modified_time` (#106)
### Security
- AI meta-generation endpoint now requires article-edit permission and enforces an HTTP timeout and status check — previously any authenticated back-end user could trigger paid API calls (#99)
- XML sitemap now excludes content above the public view level (no longer leaks registered/special-access articles) and writes atomically (#100)
### Removed
- Unused `ImageGenerator` class and `JsonLdBuilder::buildOrganization()`; generated OG images are now pruned after 30 days to bound disk usage (#104)
- Empty `src/Field` and `src/Service` stub directories; packaged the `en-US` language folder (#107)
## [01.05.00] --- 2026-06-28
### Security ### Security
- Fix JSON-LD XSS vulnerability via `</script>` injection in content data (#34) - Fix JSON-LD XSS vulnerability via `</script>` injection in content data (#34)
@@ -61,6 +95,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Database table `#__mokoog_tags` with multilingual unique key - Database table `#__mokoog_tags` with multilingual unique key
### Fixed ### Fixed
- Fix SQL driver attribute `mysql``mysqli` in component manifest preventing fresh installs
- Add exception logging to BatchController batch skip (#76) - Add exception logging to BatchController batch skip (#76)
- Align form maxlength attributes with DB schema limits (#77) - Align form maxlength attributes with DB schema limits (#77)
- Add `strip_tags()` input sanitization on OG text fields (#79) - Add `strip_tags()` input sanitization on OG text fields (#79)
+36 -18
View File
@@ -1,28 +1,46 @@
# Code of Conduct <!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License (./LICENSE).
# FILE INFORMATION
DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation
REPO: https://github.com/mokoconsulting-tech/Template-Joomla/
VERSION: 01.07.00
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Community expectations and enforcement guidelines
NOTE: Adapted with attribution from the Contributor Covenant v2.1
-->
# Contributor Covenant Code of Conduct
## Our Pledge ## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone.
We pledge to make participation in our project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards ## Our Standards
- Be empathetic and kind
- Be respectful of differing opinions
- Accept constructive feedback
- Own mistakes and learn from them
Examples of behavior that contributes to a positive environment: Unacceptable behavior includes sexualized language/imagery, trolling, harassment, doxing, and other inappropriate conduct.
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
Examples of unacceptable behavior:
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information without explicit permission
## Enforcement ## Enforcement
Report incidents to **hello@mokoconsulting.tech** or through GitHub Discussions if you prefer a community-visible approach. Private complaints will be reviewed promptly and fairly.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project team at hello@mokoconsulting.tech. All complaints will be reviewed and investigated. ## Enforcement Guidelines
1. **Correction** — Private warning
2. **Warning** — Formal warning and limited interaction
3. **Temporary Ban** — Time-boxed exclusion
4. **Permanent Ban** — Removal from the community
## Attribution ## Attribution
Adapted from the Contributor Covenant v2.1.
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.
+149 -22
View File
@@ -1,34 +1,161 @@
# Contributing to MokoJoomOpenGraph # Contributing to Moko Consulting Projects
Thank you for your interest in contributing to MokoJoomOpenGraph. Thank you for your interest in contributing. All Moko Consulting repositories follow this universal workflow and version policy.
## Getting Started ## Branching Workflow
1. Fork the repository on Gitea ```
2. Create a feature branch from `dev` (`feature/your-feature`) feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge──> main
3. Make your changes following the coding standards below ```
4. Submit a pull request targeting `dev`
## Branch Strategy ### Step by step
- `main` — stable releases only 1. **Create a feature branch** from `dev`:
- `dev` — active development ```bash
- `feature/*` — new features (target `dev`) git checkout dev && git pull
- `fix/*` — bug fixes (target `dev`) git checkout -b feature/my-change
- `hotfix/*` — urgent fixes (target `dev` or `main`) ```
## Coding Standards 2. **Work and commit** on your feature branch. Push to origin.
- PHP 8.1+ required 3. **Open a PR**: `feature/my-change` → `dev`. After review and checks, merge it.
- Follow Joomla coding standards
- SPDX license headers on all PHP files 4. **When ready for release**, open a **draft PR**: `dev` → `main`.
- Use `SubscriberInterface` for event subscription - This automatically renames the source branch to `rc` (release candidate)
- Use `bind() -> check() -> store()` for Table operations - An RC pre-release is built and uploaded
5. **Alpha and beta branches** are created by manually renaming the branch before the RC stage:
- Rename `dev` to `alpha` for early testing → alpha pre-release is built
- Rename `alpha` to `beta` for feature-complete testing → beta pre-release is built
- When the draft PR is created, the branch is renamed to `rc`
6. **Once PR checks pass** on the `rc` branch, mark the PR as ready and merge to `main`.
7. **Merging to main** triggers the stable release pipeline:
- Minor version bump (e.g., `02.09.xx` → `02.10.00`)
- Stability suffix stripped (clean version)
- Gitea release created with ZIP/tar.gz packages
- `updates.xml` updated (Joomla extensions)
- `dev` branch recreated from `main`
### Branch summary
| Branch | Purpose | Created by |
|--------|---------|-----------|
| `feature/*` | New features and fixes | Developer |
| `dev` | Integration branch | Auto-recreated after release |
| `alpha` | Alpha pre-release testing | Manual rename from `dev` |
| `beta` | Beta pre-release testing | Manual rename from `alpha` |
| `rc` | Release candidate | Auto-renamed on draft PR to main |
| `main` | Stable releases | Protected, merge only |
| `version/XX.YY.ZZ` | Archived release snapshots | Auto-created by CI |
### Protected branches
| Branch | Direct push | Merge via |
|--------|------------|-----------|
| `main` | Blocked (CI bot whitelisted) | PR merge only |
| `dev` | Blocked (CI bot whitelisted) | PR merge from feature/* |
| `rc` | Blocked (CI bot whitelisted) | Auto-created on draft PR |
| `alpha` | Blocked (CI bot whitelisted) | Manual rename |
| `beta` | Blocked (CI bot whitelisted) | Manual rename |
| `feature/*` | Open | N/A (source branch) |
## Version Policy
### Format
All versions use `XX.YY.ZZ` — three two-digit segments, zero-padded:
- **XX** — Major version (breaking changes)
- **YY** — Minor version (new features, bumped on release to main)
- **ZZ** — Patch version (auto-incremented on every push to dev/feature branches)
Rollover: patch `99` → `00` increments minor; minor `99` → `00` increments major.
### Stability suffixes
Each branch appends a suffix to indicate stability:
| Branch | Suffix | Example |
|--------|--------|---------|
| `main` | (none) | `02.09.00` |
| `dev` | `-dev` | `02.09.01-dev` |
| `feature/*` | `-dev` | `02.09.01-dev` |
| `alpha` | `-alpha` | `02.09.01-alpha` |
| `beta` | `-beta` | `02.09.01-beta` |
| `rc` | `-rc` | `02.09.01-rc` |
### Auto version bump
On every push to `dev`, `feature/*`, or `patch/*`:
1. Patch version incremented
2. Stability suffix `-dev` applied
3. All version-bearing files updated (manifests, CHANGELOG, PHP headers, etc.)
4. Commit created with `[skip ci]` to avoid loops
### Release version flow
Version bumps happen at specific release events:
| Event | Bump | Example |
|-------|------|---------|
| Feature merged to dev | Patch bump after dev release | `02.09.01-dev` → release → `02.09.02-dev` |
| Dev promoted to RC | Minor bump | `02.09.02-dev` → `02.10.00-rc` |
| RC merged to main | Minor bump | `02.10.00-rc` → `02.11.00` (stable) |
| Dev recreated from main | Patch bump | `02.11.00` → `02.11.01-dev` |
### Release stream copies
When a higher-stability release is published, copies are created for all lesser streams with the same base version:
- **RC `02.10.00-rc`** also creates: `02.10.00-dev`, `02.10.00-alpha`, `02.10.00-beta`
- **Stable `02.11.00`** also creates: `02.11.00-dev`, `02.11.00-alpha`, `02.11.00-beta`, `02.11.00-rc`
This ensures Joomla sites on ANY stability channel see the update (Joomla only shows versions higher than what's installed).
### Version files
The version tools update all files containing version stamps:
- `.mokogitea/manifest.xml` (canonical source)
- Joomla XML manifests (`<version>` tag)
- `README.md`, `CHANGELOG.md` (`VERSION:` pattern)
- `package.json`, `pyproject.toml`
- Any text file with a `VERSION: XX.YY.ZZ` label
Files synced from other repos (with a `# REPO:` header) are not touched.
## Code Standards
- **PHP**: PSR-12, tabs for indentation
- **Copyright**: all files must include the Moko Consulting copyright header
- **License**: SPDX identifier `GPL-3.0-or-later` (or as specified per repo)
- **Attribution**: use `Authored-by: Moko Consulting` in commits, not individual names
## Commit Messages
Use conventional commit format:
```
type(scope): short description
Optional body with context.
Authored-by: Moko Consulting
```
Types: `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `ci`
Special flags in commit messages:
- `[skip ci]` — skip all CI workflows
- `[skip bump]` — skip auto version bump only
## Reporting Issues ## Reporting Issues
Report bugs and feature requests via [Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomOpenGraph/issues). Use the repository's issue tracker with the appropriate template.
## License ---
By contributing, you agree that your contributions will be licensed under GPL-3.0-or-later. *Moko Consulting <hello@mokoconsulting.tech>*
+119 -1
View File
File diff suppressed because one or more lines are too long
+11 -8
View File
@@ -1,8 +1,8 @@
# MokoSuiteOpenGraph # MokoSuiteOpenGraph
<!-- VERSION: 01.04.11 --> <!-- VERSION: 01.07.00 -->
Open Graph, Twitter Card, and social sharing meta tag management for Joomla 4/5/6. Open Graph, Twitter Card, and social sharing meta tag management for Joomla 6 and higher.
## Overview ## Overview
@@ -45,21 +45,24 @@ MokoSuiteOpenGraph gives you full control over how your Joomla content appears w
- **Debug links** — Quick links to Facebook Debugger, LinkedIn Inspector, Google Rich Results - **Debug links** — Quick links to Facebook Debugger, LinkedIn Inspector, Google Rich Results
- **Live preview** — Real-time Facebook, Twitter/X, LinkedIn, Discord, Mastodon, and Slack card previews in the editor - **Live preview** — Real-time Facebook, Twitter/X, LinkedIn, Discord, Mastodon, and Slack card previews in the editor
- **Character count indicators** — Green/yellow/red warnings on OG and SEO text fields - **Character count indicators** — Green/yellow/red warnings on OG and SEO text fields
- **OG coverage dashboard** — Coverage percentage and missing field counts - **Coverage dashboard** — Default admin view: coverage donut, breakdown by content type, and a list of articles missing OG tags with quick batch-generate
- **AI meta generation** — Generate OG titles and descriptions with Claude or OpenAI - **Manual tag editor** — Create and edit individual OG tag records directly in the admin
- **Component permissions** — ACL actions (`mokoog.batch`, `mokoog.import`) configurable from the component Options → Permissions
- **AI meta generation** — Generate OG titles and descriptions with Claude or OpenAI (article-edit permission required)
### Developer Features ### Developer Features
- **REST API** — Full CRUD via Joomla Web Services (`/api/v1/mokoog/tags`) - **REST API** — Full CRUD via Joomla Web Services (`/api/v1/mokoog/tags`)
- **MokoSuiteShop integration** — Auto-generated OG/JSON-LD for product pages with pricing meta - **MokoSuiteShop integration** — Auto-generated OG/JSON-LD for product pages with pricing meta
- **Plugin event** — `onMokoOGAfterRender` for third-party plugins to add custom social tags - **Plugin event** — `onMokoOGAfterRender` for third-party plugins to add custom social tags
- **OG image generator** — Text overlay on template backgrounds with auto-resize to 1200x630 - **Per-platform image resizing** — Twitter 1200x600, Pinterest 1000x1500, WhatsApp 400x400, with auto-resize to 1200x630
- **Per-platform image resizing** — Twitter 1200x600, Pinterest 1000x1500, WhatsApp 400x400 - **XML sitemap** — Auto-generates sitemap.xml on article save; respects noindex and public access levels, written atomically
- **XML sitemap** — Auto-generates sitemap.xml on article save, respects noindex
- **OpenAPI spec** — Full REST API documentation at `openapi.yaml` - **OpenAPI spec** — Full REST API documentation at `openapi.yaml`
- **PHPUnit tests** — 16 unit tests for JsonLdBuilder schema outputs - **PHPUnit tests** — Unit tests for JsonLdBuilder schema outputs and JSON-LD script-tag escaping
## Installation ## Installation
**Requirements:** Joomla 6.0 or higher and PHP 8.2 or higher.
1. Download the latest `pkg_mokoog-*.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteOpenGraph/releases) 1. Download the latest `pkg_mokoog-*.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteOpenGraph/releases)
2. In Joomla Administrator → Extensions → Install → Upload Package File 2. In Joomla Administrator → Extensions → Install → Upload Package File
3. All plugins are enabled automatically on install 3. All plugins are enabled automatically on install
+4 -4
View File
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md PATH: /SECURITY.md
VERSION: 01.04.11 VERSION: 01.07.00
BRIEF: Security vulnerability reporting and handling policy BRIEF: Security vulnerability reporting and handling policy
--> -->
@@ -224,10 +224,10 @@ The following are explicitly out of scope:
## Metadata ## Metadata
| Field | Value | | Field | Value |
| ------------ | ------------------------------------------------------------------------------------------------------------ | | ------------ | ------------------------------------------------------------------------------------------------------------ |
| Document | Security Policy | | Document | Security Policy |
| Path | /SECURITY.md | | Path | /SECURITY.md |
| Repository | [https://github.com/mokoconsulting-tech/Template-Joomla](https://github.com/mokoconsulting-tech/Template-Joomla) | | Repository | [https://github.com/mokoconsulting-tech/Template-Joomla](https://github.com/mokoconsulting-tech/Template-Joomla) |
| Owner | Moko Consulting | | Owner | Moko Consulting |
| Scope | Security vulnerability handling | | Scope | Security vulnerability handling |
+2
View File
@@ -7,6 +7,8 @@ info:
REST API for managing Open Graph, SEO meta, and structured-data tags in REST API for managing Open Graph, SEO meta, and structured-data tags in
Joomla via the MokoSuiteOpenGraph extension. Joomla via the MokoSuiteOpenGraph extension.
**Requires Joomla 6.0 or higher.**
The API follows Joomla's Web Services conventions and returns responses in The API follows Joomla's Web Services conventions and returns responses in
[JSON:API](https://jsonapi.org/) format. All endpoints require [JSON:API](https://jsonapi.org/) format. All endpoints require
authentication via a Joomla API token. authentication via a Joomla API token.
+4 -3
View File
@@ -1,7 +1,8 @@
; MokoJoomOpenGraph - Package System Language File ; MokoSuiteOpenGraph - Package System Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
PKG_MOKOOG="MokoJoomOpenGraph" PKG_MOKOOG="MokoSuiteOpenGraph"
PKG_MOKOOG_DESCRIPTION="Complete Open Graph, Twitter Card, and social sharing meta tag management for Joomla. Control how every page appears when shared on Facebook, Twitter/X, LinkedIn, WhatsApp, and more." PKG_MOKOOG_DESCRIPTION="Complete Open Graph, Twitter Card, and social sharing meta tag management for Joomla. Control how every page appears when shared on Facebook, Twitter/X, LinkedIn, WhatsApp, and more."
PKG_MOKOOG_PHP_VERSION_ERROR="MokoJoomOpenGraph requires PHP %s or later." PKG_MOKOOG_PHP_VERSION_ERROR="MokoSuiteOpenGraph requires PHP %s or later."
PKG_MOKOOG_JOOMLA_VERSION_ERROR="MokoSuiteOpenGraph requires Joomla %s or later."
+4 -3
View File
@@ -1,7 +1,8 @@
; MokoJoomOpenGraph - Package System Language File ; MokoSuiteOpenGraph - Package System Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
PKG_MOKOOG="MokoJoomOpenGraph" PKG_MOKOOG="MokoSuiteOpenGraph"
PKG_MOKOOG_DESCRIPTION="Complete Open Graph, Twitter Card, and social sharing meta tag management for Joomla. Control how every page appears when shared on Facebook, Twitter/X, LinkedIn, WhatsApp, and more." PKG_MOKOOG_DESCRIPTION="Complete Open Graph, Twitter Card, and social sharing meta tag management for Joomla. Control how every page appears when shared on Facebook, Twitter/X, LinkedIn, WhatsApp, and more."
PKG_MOKOOG_PHP_VERSION_ERROR="MokoJoomOpenGraph requires PHP %s or later." PKG_MOKOOG_PHP_VERSION_ERROR="MokoSuiteOpenGraph requires PHP %s or later."
PKG_MOKOOG_JOOMLA_VERSION_ERROR="MokoSuiteOpenGraph requires Joomla %s or later."
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* @package MokoSuiteOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
-->
<access component="com_mokoog">
<section name="component">
<action name="core.admin" title="JACTION_ADMIN" />
<action name="core.manage" title="JACTION_MANAGE" />
<action name="core.create" title="JACTION_CREATE" />
<action name="core.delete" title="JACTION_DELETE" />
<action name="core.edit" title="JACTION_EDIT" />
<action name="core.edit.state" title="JACTION_EDITSTATE" />
<action name="mokoog.batch" title="COM_MOKOOG_ACTION_BATCH" description="COM_MOKOOG_ACTION_BATCH_DESC" />
<action name="mokoog.import" title="COM_MOKOOG_ACTION_IMPORT" description="COM_MOKOOG_ACTION_IMPORT_DESC" />
</section>
</access>
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog.api * @subpackage com_mokoog.api
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -47,7 +47,7 @@ class TagsController extends ApiController
throw new \RuntimeException('content_type and content_id are required', 400); throw new \RuntimeException('content_type and content_id are required', 400);
} }
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('id')) ->select($db->quoteName('id'))
->from($db->quoteName('#__mokoog_tags')) ->from($db->quoteName('#__mokoog_tags'))
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog.api * @subpackage com_mokoog.api
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -31,10 +31,14 @@ class JsonapiView extends BaseApiView
'og_description', 'og_description',
'og_image', 'og_image',
'og_type', 'og_type',
'og_video',
'seo_title', 'seo_title',
'meta_description', 'meta_description',
'robots', 'robots',
'canonical_url', 'canonical_url',
'event_data',
'recipe_data',
'custom_schema',
'language', 'language',
'published', 'published',
'created', 'created',
@@ -54,10 +58,14 @@ class JsonapiView extends BaseApiView
'og_description', 'og_description',
'og_image', 'og_image',
'og_type', 'og_type',
'og_video',
'seo_title', 'seo_title',
'meta_description', 'meta_description',
'robots', 'robots',
'canonical_url', 'canonical_url',
'event_data',
'recipe_data',
'custom_schema',
'language', 'language',
'published', 'published',
'created', 'created',
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* @package MokoSuiteOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
-->
<config>
<fieldset name="general">
<field
type="note"
label="COM_MOKOOG_CONFIG_NOTE_LABEL"
description="COM_MOKOOG_CONFIG_NOTE_DESC"
/>
</fieldset>
<fieldset
name="permissions"
label="JCONFIG_PERMISSIONS_LABEL"
description="JCONFIG_PERMISSIONS_DESC"
>
<field
name="rules"
type="rules"
label="JCONFIG_PERMISSIONS_LABEL"
class="inputbox"
validate="rules"
filter="rules"
component="com_mokoog"
section="component"
/>
</fieldset>
</config>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
+24 -14
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -16,13 +16,15 @@
name="content_type" name="content_type"
type="text" type="text"
label="COM_MOKOOG_FIELD_CONTENT_TYPE" label="COM_MOKOOG_FIELD_CONTENT_TYPE"
readonly="true" description="COM_MOKOOG_FIELD_CONTENT_TYPE_DESC"
required="true"
/> />
<field <field
name="content_id" name="content_id"
type="number" type="number"
label="COM_MOKOOG_FIELD_CONTENT_ID" label="COM_MOKOOG_FIELD_CONTENT_ID"
readonly="true" description="COM_MOKOOG_FIELD_CONTENT_ID_DESC"
required="true"
/> />
<field <field
name="og_title" name="og_title"
@@ -77,37 +79,45 @@
<option value="1">JPUBLISHED</option> <option value="1">JPUBLISHED</option>
<option value="0">JUNPUBLISHED</option> <option value="0">JUNPUBLISHED</option>
</field> </field>
<field
name="language"
type="contentlanguage"
label="JFIELD_LANGUAGE_LABEL"
default="*"
>
<option value="*">JALL</option>
</field>
</fieldset> </fieldset>
<fieldset name="seo" label="SEO Meta Tags"> <fieldset name="seo" label="COM_MOKOOG_FIELDSET_SEO">
<field <field
name="seo_title" name="seo_title"
type="text" type="text"
label="PLG_CONTENT_MOKOOG_FIELD_SEO_TITLE" label="COM_MOKOOG_FIELD_SEO_TITLE"
description="PLG_CONTENT_MOKOOG_FIELD_SEO_TITLE_DESC" description="COM_MOKOOG_FIELD_SEO_TITLE_DESC"
filter="string" filter="string"
maxlength="255" maxlength="70"
/> />
<field <field
name="meta_description" name="meta_description"
type="textarea" type="textarea"
label="PLG_CONTENT_MOKOOG_FIELD_META_DESCRIPTION" label="COM_MOKOOG_FIELD_META_DESCRIPTION"
description="PLG_CONTENT_MOKOOG_FIELD_META_DESCRIPTION_DESC" description="COM_MOKOOG_FIELD_META_DESCRIPTION_DESC"
filter="string" filter="string"
rows="3" rows="3"
maxlength="255" maxlength="200"
/> />
<field <field
name="robots" name="robots"
type="text" type="text"
label="PLG_CONTENT_MOKOOG_FIELD_ROBOTS" label="COM_MOKOOG_FIELD_ROBOTS"
description="PLG_CONTENT_MOKOOG_FIELD_ROBOTS_DESC" description="COM_MOKOOG_FIELD_ROBOTS_DESC"
filter="string" filter="string"
/> />
<field <field
name="canonical_url" name="canonical_url"
type="url" type="url"
label="PLG_CONTENT_MOKOOG_FIELD_CANONICAL_URL" label="COM_MOKOOG_FIELD_CANONICAL_URL"
description="PLG_CONTENT_MOKOOG_FIELD_CANONICAL_URL_DESC" description="COM_MOKOOG_FIELD_CANONICAL_URL_DESC"
filter="url" filter="url"
/> />
</fieldset> </fieldset>
@@ -1,10 +1,17 @@
; MokoJoomOpenGraph - Component Language File ; MokoSuiteOpenGraph - Component Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
COM_MOKOOG="MokoJoomOpenGraph" COM_MOKOOG="MokoSuiteOpenGraph"
COM_MOKOOG_TAGS_TITLE="MokoJoomOpenGraph - Tag Manager" COM_MOKOOG_TAGS_TITLE="MokoSuiteOpenGraph - Tag Manager"
COM_MOKOOG_SUBMENU_TAGS="Tags" COM_MOKOOG_SUBMENU_TAGS="Tags"
COM_MOKOOG_SUBMENU_DASHBOARD="Dashboard"
COM_MOKOOG_DASHBOARD_TITLE="MokoSuiteOpenGraph - Dashboard"
COM_MOKOOG_DASHBOARD_FIELD_GAPS="Field Coverage Gaps"
COM_MOKOOG_DASHBOARD_BY_TYPE="Coverage by Content Type"
COM_MOKOOG_DASHBOARD_MISSING="Articles Missing OG Tags"
COM_MOKOOG_DASHBOARD_ALL_COVERED="All published articles have OG tags."
COM_MOKOOG_DASHBOARD_MISSING_NOTE="Showing up to 20 most recent. Use Batch Generate to create OG tags for all articles at once."
COM_MOKOOG_NO_TAGS="No Open Graph tags have been created yet. Tags are created automatically when you edit articles or menu items." COM_MOKOOG_NO_TAGS="No Open Graph tags have been created yet. Tags are created automatically when you edit articles or menu items."
COM_MOKOOG_TABLE_CAPTION="Table of Open Graph tags" COM_MOKOOG_TABLE_CAPTION="Table of Open Graph tags"
COM_MOKOOG_AUTO_GENERATED="auto-generated" COM_MOKOOG_AUTO_GENERATED="auto-generated"
@@ -66,3 +73,27 @@ COM_MOKOOG_COVERAGE_ARTICLES="%d of %d articles have OG tags"
COM_MOKOOG_COVERAGE_MISSING_TITLE="%d tags missing custom title" COM_MOKOOG_COVERAGE_MISSING_TITLE="%d tags missing custom title"
COM_MOKOOG_COVERAGE_MISSING_DESC="%d tags missing custom description" COM_MOKOOG_COVERAGE_MISSING_DESC="%d tags missing custom description"
COM_MOKOOG_COVERAGE_MISSING_IMAGE="%d tags missing custom image" COM_MOKOOG_COVERAGE_MISSING_IMAGE="%d tags missing custom image"
; Single-tag edit form
COM_MOKOOG_TAG_NEW="MokoSuiteOpenGraph - New OG Tag"
COM_MOKOOG_TAG_EDIT="MokoSuiteOpenGraph - Edit OG Tag"
COM_MOKOOG_TAB_DETAILS="Details"
COM_MOKOOG_FIELDSET_SEO="SEO Meta Tags"
COM_MOKOOG_FIELD_CONTENT_TYPE_DESC="The content type this OG tag applies to (e.g. com_content, menu, com_content.category)."
COM_MOKOOG_FIELD_CONTENT_ID_DESC="The ID of the content item this OG tag applies to."
COM_MOKOOG_FIELD_SEO_TITLE="SEO Title"
COM_MOKOOG_FIELD_SEO_TITLE_DESC="Overrides the page &lt;title&gt; tag (max 70 characters)."
COM_MOKOOG_FIELD_META_DESCRIPTION="Meta Description"
COM_MOKOOG_FIELD_META_DESCRIPTION_DESC="Overrides the page meta description (max 200 characters)."
COM_MOKOOG_FIELD_ROBOTS="Robots"
COM_MOKOOG_FIELD_ROBOTS_DESC="Per-page robots directive, e.g. noindex, nofollow."
COM_MOKOOG_FIELD_CANONICAL_URL="Canonical URL"
COM_MOKOOG_FIELD_CANONICAL_URL_DESC="Overrides the canonical URL for this content item (http/https only)."
; ACL actions (access.xml) and component options (config.xml)
COM_MOKOOG_ACTION_BATCH="Batch Generate OG Tags"
COM_MOKOOG_ACTION_BATCH_DESC="Allows users in this group to run batch OG tag generation."
COM_MOKOOG_ACTION_IMPORT="Import / Export OG Tags"
COM_MOKOOG_ACTION_IMPORT_DESC="Allows users in this group to import and export OG tags via CSV."
COM_MOKOOG_CONFIG_NOTE_LABEL="Where are the settings?"
COM_MOKOOG_CONFIG_NOTE_DESC="Open Graph and SEO settings are configured in the System - MokoSuiteOpenGraph plugin (Extensions &#8594; Plugins). This screen manages component permissions only."
@@ -1,6 +1,6 @@
; MokoJoomOpenGraph - Component System Language File ; MokoSuiteOpenGraph - Component System Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
COM_MOKOOG="MokoJoomOpenGraph" COM_MOKOOG="MokoSuiteOpenGraph"
COM_MOKOOG_DESCRIPTION="Manage Open Graph and social sharing tags for all your content. View, edit, and batch-process OG metadata." COM_MOKOOG_DESCRIPTION="Manage Open Graph and social sharing tags for all your content. View, edit, and batch-process OG metadata."
@@ -1,10 +1,17 @@
; MokoJoomOpenGraph - Component Language File ; MokoSuiteOpenGraph - Component Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
COM_MOKOOG="MokoJoomOpenGraph" COM_MOKOOG="MokoSuiteOpenGraph"
COM_MOKOOG_TAGS_TITLE="MokoJoomOpenGraph - Tag Manager" COM_MOKOOG_TAGS_TITLE="MokoSuiteOpenGraph - Tag Manager"
COM_MOKOOG_SUBMENU_TAGS="Tags" COM_MOKOOG_SUBMENU_TAGS="Tags"
COM_MOKOOG_SUBMENU_DASHBOARD="Dashboard"
COM_MOKOOG_DASHBOARD_TITLE="MokoSuiteOpenGraph - Dashboard"
COM_MOKOOG_DASHBOARD_FIELD_GAPS="Field Coverage Gaps"
COM_MOKOOG_DASHBOARD_BY_TYPE="Coverage by Content Type"
COM_MOKOOG_DASHBOARD_MISSING="Articles Missing OG Tags"
COM_MOKOOG_DASHBOARD_ALL_COVERED="All published articles have OG tags."
COM_MOKOOG_DASHBOARD_MISSING_NOTE="Showing up to 20 most recent. Use Batch Generate to create OG tags for all articles at once."
COM_MOKOOG_NO_TAGS="No Open Graph tags have been created yet. Tags are created automatically when you edit articles or menu items." COM_MOKOOG_NO_TAGS="No Open Graph tags have been created yet. Tags are created automatically when you edit articles or menu items."
COM_MOKOOG_TABLE_CAPTION="Table of Open Graph tags" COM_MOKOOG_TABLE_CAPTION="Table of Open Graph tags"
COM_MOKOOG_AUTO_GENERATED="auto-generated" COM_MOKOOG_AUTO_GENERATED="auto-generated"
@@ -66,3 +73,27 @@ COM_MOKOOG_COVERAGE_ARTICLES="%d of %d articles have OG tags"
COM_MOKOOG_COVERAGE_MISSING_TITLE="%d tags missing custom title" COM_MOKOOG_COVERAGE_MISSING_TITLE="%d tags missing custom title"
COM_MOKOOG_COVERAGE_MISSING_DESC="%d tags missing custom description" COM_MOKOOG_COVERAGE_MISSING_DESC="%d tags missing custom description"
COM_MOKOOG_COVERAGE_MISSING_IMAGE="%d tags missing custom image" COM_MOKOOG_COVERAGE_MISSING_IMAGE="%d tags missing custom image"
; Single-tag edit form
COM_MOKOOG_TAG_NEW="MokoSuiteOpenGraph - New OG Tag"
COM_MOKOOG_TAG_EDIT="MokoSuiteOpenGraph - Edit OG Tag"
COM_MOKOOG_TAB_DETAILS="Details"
COM_MOKOOG_FIELDSET_SEO="SEO Meta Tags"
COM_MOKOOG_FIELD_CONTENT_TYPE_DESC="The content type this OG tag applies to (e.g. com_content, menu, com_content.category)."
COM_MOKOOG_FIELD_CONTENT_ID_DESC="The ID of the content item this OG tag applies to."
COM_MOKOOG_FIELD_SEO_TITLE="SEO Title"
COM_MOKOOG_FIELD_SEO_TITLE_DESC="Overrides the page &lt;title&gt; tag (max 70 characters)."
COM_MOKOOG_FIELD_META_DESCRIPTION="Meta Description"
COM_MOKOOG_FIELD_META_DESCRIPTION_DESC="Overrides the page meta description (max 200 characters)."
COM_MOKOOG_FIELD_ROBOTS="Robots"
COM_MOKOOG_FIELD_ROBOTS_DESC="Per-page robots directive, e.g. noindex, nofollow."
COM_MOKOOG_FIELD_CANONICAL_URL="Canonical URL"
COM_MOKOOG_FIELD_CANONICAL_URL_DESC="Overrides the canonical URL for this content item (http/https only)."
; ACL actions (access.xml) and component options (config.xml)
COM_MOKOOG_ACTION_BATCH="Batch Generate OG Tags"
COM_MOKOOG_ACTION_BATCH_DESC="Allows users in this group to run batch OG tag generation."
COM_MOKOOG_ACTION_IMPORT="Import / Export OG Tags"
COM_MOKOOG_ACTION_IMPORT_DESC="Allows users in this group to import and export OG tags via CSV."
COM_MOKOOG_CONFIG_NOTE_LABEL="Where are the settings?"
COM_MOKOOG_CONFIG_NOTE_DESC="Open Graph and SEO settings are configured in the System - MokoSuiteOpenGraph plugin (Extensions &#8594; Plugins). This screen manages component permissions only."
@@ -1,6 +1,6 @@
; MokoJoomOpenGraph - Component System Language File ; MokoSuiteOpenGraph - Component System Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
COM_MOKOOG="MokoJoomOpenGraph" COM_MOKOOG="MokoSuiteOpenGraph"
COM_MOKOOG_DESCRIPTION="Manage Open Graph and social sharing tags for all your content. View, edit, and batch-process OG metadata." COM_MOKOOG_DESCRIPTION="Manage Open Graph and social sharing tags for all your content. View, edit, and batch-process OG metadata."
+13 -5
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -8,7 +8,7 @@
--> -->
<extension type="component" method="upgrade"> <extension type="component" method="upgrade">
<name>com_mokoog</name> <name>com_mokoog</name>
<version>01.04.11</version> <version>01.07.00</version>
<creationDate>2026-05-23</creationDate> <creationDate>2026-05-23</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -23,17 +23,17 @@
<install> <install>
<sql> <sql>
<file driver="mysql" charset="utf8">sql/install.mysql.sql</file> <file driver="mysqli" charset="utf8">sql/install.mysql.sql</file>
</sql> </sql>
</install> </install>
<uninstall> <uninstall>
<sql> <sql>
<file driver="mysql" charset="utf8">sql/uninstall.mysql.sql</file> <file driver="mysqli" charset="utf8">sql/uninstall.mysql.sql</file>
</sql> </sql>
</uninstall> </uninstall>
<update> <update>
<schemas> <schemas>
<schemapath type="mysql">sql/updates/mysql</schemapath> <schemapath type="mysqli">sql/updates/mysql</schemapath>
</schemas> </schemas>
</update> </update>
@@ -50,6 +50,8 @@
<folder>View</folder> <folder>View</folder>
</files> </files>
<files folder="tmpl"> <files folder="tmpl">
<folder>dashboard</folder>
<folder>tag</folder>
<folder>tags</folder> <folder>tags</folder>
</files> </files>
<files folder="sql"> <files folder="sql">
@@ -63,9 +65,15 @@
</files> </files>
<files folder="language"> <files folder="language">
<folder>en-GB</folder> <folder>en-GB</folder>
<folder>en-US</folder>
</files>
<files>
<filename>access.xml</filename>
<filename>config.xml</filename>
</files> </files>
<menu img="class:bookmark">COM_MOKOOG</menu> <menu img="class:bookmark">COM_MOKOOG</menu>
<submenu> <submenu>
<menu link="option=com_mokoog&amp;view=dashboard">COM_MOKOOG_SUBMENU_DASHBOARD</menu>
<menu link="option=com_mokoog&amp;view=tags">COM_MOKOOG_SUBMENU_TAGS</menu> <menu link="option=com_mokoog&amp;view=tags">COM_MOKOOG_SUBMENU_TAGS</menu>
</submenu> </submenu>
</administration> </administration>
+3 -3
View File
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -23,7 +23,7 @@ class Com_MokoOGInstallerScript
*/ */
public function install(InstallerAdapter $parent): void public function install(InstallerAdapter $parent): void
{ {
echo '<p>MokoJoomOpenGraph component installed successfully.</p>'; echo '<p>MokoSuiteOpenGraph component installed successfully.</p>';
} }
/** /**
@@ -35,6 +35,6 @@ class Com_MokoOGInstallerScript
*/ */
public function update(InstallerAdapter $parent): void public function update(InstallerAdapter $parent): void
{ {
echo '<p>MokoJoomOpenGraph component updated successfully.</p>'; echo '<p>MokoSuiteOpenGraph component updated successfully.</p>';
} }
} }
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -1,5 +1,5 @@
-- --
-- MokoJoomOpenGraph - Database Schema -- MokoSuiteOpenGraph - Database Schema
-- Copyright (C) 2026 Moko Consulting. All rights reserved. -- Copyright (C) 2026 Moko Consulting. All rights reserved.
-- License: GPL-3.0-or-later -- License: GPL-3.0-or-later
-- --
@@ -1,5 +1,5 @@
-- --
-- MokoJoomOpenGraph - Uninstall -- MokoSuiteOpenGraph - Uninstall
-- --
DROP TABLE IF EXISTS `#__mokoog_tags`; DROP TABLE IF EXISTS `#__mokoog_tags`;
@@ -1,5 +1,5 @@
-- --
-- MokoJoomOpenGraph 01.01.00 — Add SEO meta management columns -- MokoSuiteOpenGraph 01.01.00 — Add SEO meta management columns
-- --
ALTER TABLE `#__mokoog_tags` ALTER TABLE `#__mokoog_tags`
@@ -1,5 +1,5 @@
-- --
-- MokoJoomOpenGraph 01.02.00 — Add multilingual OG tag support -- MokoSuiteOpenGraph 01.02.00 — Add multilingual OG tag support
-- --
ALTER TABLE `#__mokoog_tags` ALTER TABLE `#__mokoog_tags`
@@ -1,5 +1,5 @@
-- --
-- MokoJoomOpenGraph 01.03.00 - Add og_video column -- MokoSuiteOpenGraph 01.03.00 - Add og_video column
-- --
ALTER TABLE `#__mokoog_tags` ADD COLUMN `og_video` VARCHAR(512) NOT NULL DEFAULT '' AFTER `og_type`; ALTER TABLE `#__mokoog_tags` ADD COLUMN `og_video` VARCHAR(512) NOT NULL DEFAULT '' AFTER `og_type`;
@@ -1,5 +1,5 @@
-- --
-- MokoJoomOpenGraph 01.04.00 - Add event_data and recipe_data columns -- MokoSuiteOpenGraph 01.04.00 - Add event_data and recipe_data columns
-- --
ALTER TABLE `#__mokoog_tags` ADD COLUMN `event_data` TEXT NULL AFTER `og_video`; ALTER TABLE `#__mokoog_tags` ADD COLUMN `event_data` TEXT NULL AFTER `og_video`;
@@ -0,0 +1 @@
/* 01.04.12 — no schema changes */
@@ -0,0 +1 @@
/* 01.04.13 — no schema changes */
@@ -0,0 +1 @@
/* 01.04.14 — no schema changes */
@@ -0,0 +1 @@
/* 01.04.15 — no schema changes */
@@ -0,0 +1 @@
/* 01.04.16 — no schema changes */
@@ -0,0 +1 @@
/* 01.04.17 — no schema changes */
@@ -0,0 +1 @@
/* 01.04.18 — no schema changes */
@@ -0,0 +1 @@
/* 01.05.01 — no schema changes */
@@ -0,0 +1 @@
/* 01.05.02 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.00 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.02 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.03 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.04 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.05 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.06 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.07 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.08 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.09 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.10 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.11 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.12 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.13 — no schema changes */
@@ -0,0 +1 @@
/* 01.07.00 — no schema changes */
@@ -27,13 +27,16 @@ class BatchController extends BaseController
*/ */
public function count(): void public function count(): void
{ {
Session::checkToken('get') || jexit(Text::_('JINVALID_TOKEN')); Session::checkToken('get') || throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
if (!Factory::getApplication()->getIdentity()->authorise('core.create', 'com_mokoog')) { $identity = Factory::getApplication()->getIdentity();
if (!$identity->authorise('mokoog.batch', 'com_mokoog')
&& !$identity->authorise('core.create', 'com_mokoog')) {
throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
} }
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('COUNT(*)') ->select('COUNT(*)')
->from($db->quoteName('#__content', 'c')) ->from($db->quoteName('#__content', 'c'))
@@ -60,16 +63,19 @@ class BatchController extends BaseController
*/ */
public function process(): void public function process(): void
{ {
Session::checkToken('get') || jexit(Text::_('JINVALID_TOKEN')); Session::checkToken('get') || throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
if (!Factory::getApplication()->getIdentity()->authorise('core.create', 'com_mokoog')) { $identity = Factory::getApplication()->getIdentity();
if (!$identity->authorise('mokoog.batch', 'com_mokoog')
&& !$identity->authorise('core.create', 'com_mokoog')) {
throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
} }
$app = Factory::getApplication(); $app = Factory::getApplication();
$limit = min($app->getInput()->getInt('limit', 50), 200); $limit = min($app->getInput()->getInt('limit', 50), 200);
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName([ ->select($db->quoteName([
'c.id', 'c.title', 'c.metadesc', 'c.introtext', 'c.fulltext', 'c.images', 'c.id', 'c.title', 'c.metadesc', 'c.introtext', 'c.fulltext', 'c.images',
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -21,5 +21,5 @@ class DisplayController extends BaseController
* *
* @var string * @var string
*/ */
protected $default_view = 'tags'; protected $default_view = 'dashboard';
} }
@@ -36,14 +36,14 @@ class ImportExportController extends BaseController
*/ */
public function export(): void public function export(): void
{ {
Session::checkToken('get') || jexit(Text::_('JINVALID_TOKEN')); Session::checkToken('get') || throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
if (!Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_mokoog')) { if (!Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_mokoog')) {
throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
} }
$app = Factory::getApplication(); $app = Factory::getApplication();
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
// Join with #__content to get article titles for reference // Join with #__content to get article titles for reference
$query = $db->getQuery(true) $query = $db->getQuery(true)
@@ -60,6 +60,10 @@ class ImportExportController extends BaseController
$db->quoteName('t.robots'), $db->quoteName('t.robots'),
$db->quoteName('t.canonical_url'), $db->quoteName('t.canonical_url'),
$db->quoteName('t.language'), $db->quoteName('t.language'),
$db->quoteName('t.og_video'),
$db->quoteName('t.event_data'),
$db->quoteName('t.recipe_data'),
$db->quoteName('t.custom_schema'),
]) ])
->from($db->quoteName('#__mokoog_tags', 't')) ->from($db->quoteName('#__mokoog_tags', 't'))
->leftJoin( ->leftJoin(
@@ -84,7 +88,7 @@ class ImportExportController extends BaseController
'content_type', 'content_id', 'article_title', 'content_type', 'content_id', 'article_title',
'og_title', 'og_description', 'og_image', 'og_type', 'og_title', 'og_description', 'og_image', 'og_type',
'seo_title', 'meta_description', 'robots', 'canonical_url', 'seo_title', 'meta_description', 'robots', 'canonical_url',
'language', 'language', 'og_video', 'event_data', 'recipe_data', 'custom_schema',
]); ]);
foreach ($rows as $row) { foreach ($rows as $row) {
@@ -102,11 +106,12 @@ class ImportExportController extends BaseController
*/ */
public function import(): void public function import(): void
{ {
Session::checkToken() || jexit(Text::_('JINVALID_TOKEN')); Session::checkToken() || throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
$identity = Factory::getApplication()->getIdentity(); $identity = Factory::getApplication()->getIdentity();
if (!$identity->authorise('core.create', 'com_mokoog') || !$identity->authorise('core.edit', 'com_mokoog')) { if (!$identity->authorise('mokoog.import', 'com_mokoog')
&& !($identity->authorise('core.create', 'com_mokoog') && $identity->authorise('core.edit', 'com_mokoog'))) {
throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
} }
@@ -161,7 +166,7 @@ class ImportExportController extends BaseController
return; return;
} }
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$header = fgetcsv($handle); $header = fgetcsv($handle);
$created = 0; $created = 0;
$updated = 0; $updated = 0;
@@ -187,6 +192,10 @@ class ImportExportController extends BaseController
$robots = trim($row[9] ?? ''); $robots = trim($row[9] ?? '');
$canonicalUrl = trim($row[10] ?? ''); $canonicalUrl = trim($row[10] ?? '');
$language = trim($row[11] ?? '*'); $language = trim($row[11] ?? '*');
$ogVideo = $this->sanitizeUrl($row[12] ?? '');
$eventData = $this->validateJsonField($row[13] ?? '');
$recipeData = $this->validateJsonField($row[14] ?? '');
$customSchema = $this->validateJsonField($row[15] ?? '');
// Validate language tag format (e.g., 'en-GB', '*') // Validate language tag format (e.g., 'en-GB', '*')
if ($language !== '*' && !preg_match('/^[a-z]{2,3}-[A-Z]{2}$/', $language)) { if ($language !== '*' && !preg_match('/^[a-z]{2,3}-[A-Z]{2}$/', $language)) {
@@ -229,6 +238,10 @@ class ImportExportController extends BaseController
'robots' => $robots, 'robots' => $robots,
'canonical_url' => $canonicalUrl, 'canonical_url' => $canonicalUrl,
'language' => $language, 'language' => $language,
'og_video' => $ogVideo,
'event_data' => $eventData,
'recipe_data' => $recipeData,
'custom_schema' => $customSchema,
'published' => 1, 'published' => 1,
'modified' => $now, 'modified' => $now,
]; ];
@@ -252,4 +265,45 @@ class ImportExportController extends BaseController
); );
$app->redirect('index.php?option=com_mokoog&view=tags'); $app->redirect('index.php?option=com_mokoog&view=tags');
} }
/**
* Validate a JSON field — returns trimmed JSON only if it is an object/array.
*
* Scalars and invalid JSON are dropped to '' so an import can never inject a
* payload that crashes the frontend JSON-LD renderer.
*
* @param string $value Raw CSV cell value
*
* @return string
*/
private function validateJsonField(string $value): string
{
$value = trim($value);
if ($value === '' || !\is_array(json_decode($value, true))) {
return '';
}
return $value;
}
/**
* Sanitize a URL to only allow http/https schemes.
*
* @param string $url Raw CSV cell value
*
* @return string Sanitized URL or empty string
*/
private function sanitizeUrl(string $url): string
{
$url = trim($url);
if ($url === '') {
return '';
}
$scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME));
return \in_array($scheme, ['http', 'https'], true) ? $url : '';
}
} }
@@ -0,0 +1,31 @@
<?php
/**
* @package MokoSuiteOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Joomla\Component\MokoOG\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\FormController;
/**
* Controller for a single OG tag record.
*
* Provides the standard add/edit/save/apply/cancel tasks via FormController,
* backed by the existing TagModel (AdminModel) and TagTable.
*/
class TagController extends FormController
{
/**
* The list view to redirect to after save/cancel.
*
* @var string
*/
protected $view_list = 'tags';
}
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -0,0 +1,159 @@
<?php
/**
* @package MokoSuiteOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Joomla\Component\MokoOG\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
/**
* Read-only model providing OG tag coverage metrics for the dashboard.
*/
class DashboardModel extends BaseDatabaseModel
{
/**
* Overall coverage statistics for com_content articles.
*
* @return array{total:int, with_og:int, coverage:int, missing_title:int, missing_description:int, missing_image:int}
*/
public function getStats(): array
{
$db = $this->getDatabase();
$total = $this->countContent();
$withOg = $this->countDistinct();
$missingTitle = $this->countEmptyField('og_title');
$missingDesc = $this->countEmptyField('og_description');
$missingImage = $this->countEmptyField('og_image');
return [
'total' => $total,
'with_og' => $withOg,
'coverage' => $total > 0 ? (int) round(($withOg / $total) * 100) : 0,
'missing_title' => $missingTitle,
'missing_description' => $missingDesc,
'missing_image' => $missingImage,
];
}
/**
* Coverage broken down by content_type.
*
* @return array Rows of {content_type, total, with_title, with_image}
*/
public function getCoverageByType(): array
{
$db = $this->getDatabase();
$empty = $db->quote('');
$query = $db->getQuery(true)
->select([
$db->quoteName('content_type'),
'COUNT(*) AS ' . $db->quoteName('total'),
'SUM(CASE WHEN ' . $db->quoteName('og_title') . ' <> ' . $empty . ' THEN 1 ELSE 0 END) AS ' . $db->quoteName('with_title'),
'SUM(CASE WHEN ' . $db->quoteName('og_image') . ' <> ' . $empty . ' THEN 1 ELSE 0 END) AS ' . $db->quoteName('with_image'),
])
->from($db->quoteName('#__mokoog_tags'))
->where($db->quoteName('published') . ' = 1')
->group($db->quoteName('content_type'))
->order($db->quoteName('content_type') . ' ASC');
$db->setQuery($query);
return $db->loadObjectList() ?: [];
}
/**
* Published articles that have no OG tag yet.
*
* @param int $limit Maximum rows to return
*
* @return array Rows of {id, title}
*/
public function getMissingArticles(int $limit = 20): array
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select([$db->quoteName('c.id'), $db->quoteName('c.title')])
->from($db->quoteName('#__content', 'c'))
->leftJoin(
$db->quoteName('#__mokoog_tags', 't')
. ' ON ' . $db->quoteName('t.content_type') . ' = ' . $db->quote('com_content')
. ' AND ' . $db->quoteName('t.content_id') . ' = ' . $db->quoteName('c.id')
)
->where($db->quoteName('c.state') . ' = 1')
->where($db->quoteName('t.id') . ' IS NULL')
->order($db->quoteName('c.id') . ' DESC');
$db->setQuery($query, 0, max(1, $limit));
return $db->loadObjectList() ?: [];
}
/**
* Count published com_content articles.
*/
private function countContent(): int
{
$db = $this->getDatabase();
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__content'))
->where($db->quoteName('state') . ' = 1')
);
return (int) $db->loadResult();
}
/**
* Count distinct articles that have at least one published OG tag.
*/
private function countDistinct(): int
{
$db = $this->getDatabase();
$db->setQuery(
$db->getQuery(true)
->select('COUNT(DISTINCT ' . $db->quoteName('content_id') . ')')
->from($db->quoteName('#__mokoog_tags'))
->where($db->quoteName('content_type') . ' = ' . $db->quote('com_content'))
->where($db->quoteName('published') . ' = 1')
);
return (int) $db->loadResult();
}
/**
* Count published OG tag rows whose given field is empty.
*
* @param string $field One of og_title, og_description, og_image
*/
private function countEmptyField(string $field): int
{
// Whitelist the column name — it is never user input here, but keep it strict.
if (!\in_array($field, ['og_title', 'og_description', 'og_image'], true)) {
return 0;
}
$db = $this->getDatabase();
$db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokoog_tags'))
->where($db->quoteName('content_type') . ' = ' . $db->quote('com_content'))
->where($db->quoteName('published') . ' = 1')
->where($db->quoteName($field) . ' = ' . $db->quote(''))
);
return (int) $db->loadResult();
}
}
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -0,0 +1,76 @@
<?php
/**
* @package MokoSuiteOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Joomla\Component\MokoOG\Administrator\View\Dashboard;
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
/**
* Dashboard view — OG tag coverage metrics.
*/
class HtmlView extends BaseHtmlView
{
/**
* Overall coverage stats.
*
* @var array
*/
protected $stats = [];
/**
* Coverage broken down by content_type.
*
* @var array
*/
protected $byType = [];
/**
* Published articles missing an OG tag.
*
* @var array
*/
protected $missing = [];
/**
* Display the view.
*
* @param string $tpl Template name
*
* @return void
*/
public function display($tpl = null): void
{
/** @var \Joomla\Component\MokoOG\Administrator\Model\DashboardModel $model */
$model = $this->getModel();
$this->stats = $model->getStats();
$this->byType = $model->getCoverageByType();
$this->missing = $model->getMissingArticles(20);
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the toolbar.
*
* @return void
*/
protected function addToolbar(): void
{
ToolbarHelper::title(Text::_('COM_MOKOOG_DASHBOARD_TITLE'), 'bookmark');
ToolbarHelper::preferences('com_mokoog');
}
}
@@ -0,0 +1,76 @@
<?php
/**
* @package MokoSuiteOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Joomla\Component\MokoOG\Administrator\View\Tag;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
/**
* Edit view for a single OG tag record.
*/
class HtmlView extends BaseHtmlView
{
/**
* The edit form.
*
* @var \Joomla\CMS\Form\Form
*/
protected $form;
/**
* The item being edited.
*
* @var object
*/
protected $item;
/**
* Display the view.
*
* @param string $tpl Template name
*
* @return void
*/
public function display($tpl = null): void
{
$this->form = $this->get('Form');
$this->item = $this->get('Item');
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the edit toolbar.
*
* @return void
*/
protected function addToolbar(): void
{
Factory::getApplication()->getInput()->set('hidemainmenu', true);
$isNew = empty($this->item->id);
ToolbarHelper::title(
Text::_($isNew ? 'COM_MOKOOG_TAG_NEW' : 'COM_MOKOOG_TAG_EDIT'),
'bookmark'
);
ToolbarHelper::apply('tag.apply');
ToolbarHelper::save('tag.save');
ToolbarHelper::cancel('tag.cancel', $isNew ? 'JTOOLBAR_CANCEL' : 'JTOOLBAR_CLOSE');
}
}
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -81,8 +81,11 @@ class HtmlView extends BaseHtmlView
protected function addToolbar(): void protected function addToolbar(): void
{ {
ToolbarHelper::title(Text::_('COM_MOKOOG_TAGS_TITLE'), 'bookmark'); ToolbarHelper::title(Text::_('COM_MOKOOG_TAGS_TITLE'), 'bookmark');
ToolbarHelper::addNew('tag.add');
ToolbarHelper::editList('tag.edit');
ToolbarHelper::custom('batch.generate', 'refresh', '', 'COM_MOKOOG_TOOLBAR_BATCH_GENERATE', false); ToolbarHelper::custom('batch.generate', 'refresh', '', 'COM_MOKOOG_TOOLBAR_BATCH_GENERATE', false);
ToolbarHelper::custom('importexport.export', 'download', '', 'COM_MOKOOG_TOOLBAR_EXPORT', false); ToolbarHelper::custom('importexport.export', 'download', '', 'COM_MOKOOG_TOOLBAR_EXPORT', false);
ToolbarHelper::custom('mokoog.showimport', 'upload', '', 'COM_MOKOOG_TOOLBAR_IMPORT', false);
ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'tags.delete'); ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'tags.delete');
ToolbarHelper::preferences('com_mokoog'); ToolbarHelper::preferences('com_mokoog');
} }
@@ -0,0 +1,142 @@
<?php
/**
* @package MokoSuiteOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
/** @var \Joomla\Component\MokoOG\Administrator\View\Dashboard\HtmlView $this */
$s = $this->stats;
$coverage = (int) ($s['coverage'] ?? 0);
$total = (int) ($s['total'] ?? 0);
$withOg = (int) ($s['with_og'] ?? 0);
$colorClass = $coverage >= 80 ? 'text-success' : ($coverage >= 50 ? 'text-warning' : 'text-danger');
$stroke = $coverage >= 80 ? '#198754' : ($coverage >= 50 ? '#ffc107' : '#dc3545');
$r = 54.0;
$circ = 2 * M_PI * $r;
$dash = round($circ * $coverage / 100, 2);
$gap = round($circ - $dash, 2);
?>
<div class="p-3">
<div class="row">
<!-- Coverage donut -->
<div class="col-lg-4 mb-3">
<div class="card h-100">
<div class="card-body text-center">
<h4 class="card-title"><?php echo Text::_('COM_MOKOOG_COVERAGE_PERCENT'); ?></h4>
<svg width="160" height="160" viewBox="0 0 140 140" role="img"
aria-label="<?php echo $coverage; ?>%" class="<?php echo $colorClass; ?>">
<circle cx="70" cy="70" r="54" fill="none" stroke="#e9ecef" stroke-width="14"></circle>
<circle cx="70" cy="70" r="54" fill="none" stroke="<?php echo $stroke; ?>" stroke-width="14"
stroke-dasharray="<?php echo $dash; ?> <?php echo $gap; ?>"
stroke-linecap="round" transform="rotate(-90 70 70)"></circle>
<text x="70" y="80" text-anchor="middle" font-size="30" font-weight="bold" fill="currentColor"><?php echo $coverage; ?>%</text>
</svg>
<p class="mt-2 mb-0"><?php echo Text::sprintf('COM_MOKOOG_COVERAGE_ARTICLES', $withOg, $total); ?></p>
</div>
</div>
</div>
<!-- Missing fields -->
<div class="col-lg-8 mb-3">
<div class="card h-100">
<div class="card-body">
<h4 class="card-title"><?php echo Text::_('COM_MOKOOG_DASHBOARD_FIELD_GAPS'); ?></h4>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between">
<span><?php echo Text::_('COM_MOKOOG_HEADING_OG_TITLE'); ?></span>
<span class="badge bg-<?php echo ($s['missing_title'] ?? 0) ? 'warning text-dark' : 'success'; ?>"><?php echo Text::sprintf('COM_MOKOOG_COVERAGE_MISSING_TITLE', (int) ($s['missing_title'] ?? 0)); ?></span>
</li>
<li class="list-group-item d-flex justify-content-between">
<span><?php echo Text::_('COM_MOKOOG_FIELD_OG_DESCRIPTION'); ?></span>
<span class="badge bg-<?php echo ($s['missing_description'] ?? 0) ? 'warning text-dark' : 'success'; ?>"><?php echo Text::sprintf('COM_MOKOOG_COVERAGE_MISSING_DESC', (int) ($s['missing_description'] ?? 0)); ?></span>
</li>
<li class="list-group-item d-flex justify-content-between">
<span><?php echo Text::_('COM_MOKOOG_HEADING_IMAGE'); ?></span>
<span class="badge bg-<?php echo ($s['missing_image'] ?? 0) ? 'warning text-dark' : 'success'; ?>"><?php echo Text::sprintf('COM_MOKOOG_COVERAGE_MISSING_IMAGE', (int) ($s['missing_image'] ?? 0)); ?></span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Coverage by content type -->
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h4 class="card-title"><?php echo Text::_('COM_MOKOOG_DASHBOARD_BY_TYPE'); ?></h4>
<?php if (empty($this->byType)) : ?>
<p class="text-muted mb-0"><?php echo Text::_('COM_MOKOOG_NO_TAGS'); ?></p>
<?php else : ?>
<table class="table table-sm mb-0">
<thead>
<tr>
<th><?php echo Text::_('COM_MOKOOG_HEADING_CONTENT_TYPE'); ?></th>
<th class="text-end"><?php echo Text::_('JGRID_HEADING_ID'); ?></th>
<th class="text-end"><?php echo Text::_('COM_MOKOOG_HEADING_OG_TITLE'); ?></th>
<th class="text-end"><?php echo Text::_('COM_MOKOOG_HEADING_IMAGE'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($this->byType as $row) : ?>
<tr>
<td><?php echo $this->escape($row->content_type); ?></td>
<td class="text-end"><?php echo (int) $row->total; ?></td>
<td class="text-end"><?php echo (int) $row->with_title; ?></td>
<td class="text-end"><?php echo (int) $row->with_image; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
</div>
<!-- Articles missing OG tags -->
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h4 class="card-title mb-0"><?php echo Text::_('COM_MOKOOG_DASHBOARD_MISSING'); ?></h4>
<a class="btn btn-sm btn-primary" href="<?php echo Route::_('index.php?option=com_mokoog&view=tags'); ?>">
<span class="icon-refresh" aria-hidden="true"></span>
<?php echo Text::_('COM_MOKOOG_TOOLBAR_BATCH_GENERATE'); ?>
</a>
</div>
<?php if (empty($this->missing)) : ?>
<p class="text-success mb-0">
<span class="icon-check" aria-hidden="true"></span>
<?php echo Text::_('COM_MOKOOG_DASHBOARD_ALL_COVERED'); ?>
</p>
<?php else : ?>
<ul class="list-group list-group-flush">
<?php foreach ($this->missing as $article) : ?>
<li class="list-group-item py-1">
<a href="<?php echo Route::_('index.php?option=com_content&task=article.edit&id=' . (int) $article->id); ?>">
<?php echo $this->escape($article->title); ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<small class="text-muted d-block mt-2"><?php echo Text::_('COM_MOKOOG_DASHBOARD_MISSING_NOTE'); ?></small>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,41 @@
<?php
/**
* @package MokoSuiteOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
/** @var \Joomla\Component\MokoOG\Administrator\View\Tag\HtmlView $this */
HTMLHelper::_('behavior.formvalidator');
?>
<form action="<?php echo Route::_('index.php?option=com_mokoog&view=tag&layout=edit&id=' . (int) ($this->item->id ?? 0)); ?>"
method="post" name="adminForm" id="adminForm" class="form-validate" aria-label="<?php echo $this->escape(Text::_('COM_MOKOOG_TAG_EDIT')); ?>">
<div class="row">
<div class="col-lg-9">
<?php echo HTMLHelper::_('uitab.startTabSet', 'mokoogTab', ['active' => 'details']); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'mokoogTab', 'details', Text::_('COM_MOKOOG_TAB_DETAILS')); ?>
<?php echo $this->form->renderFieldset('details'); ?>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.addTab', 'mokoogTab', 'seo', Text::_('COM_MOKOOG_FIELDSET_SEO')); ?>
<?php echo $this->form->renderFieldset('seo'); ?>
<?php echo HTMLHelper::_('uitab.endTab'); ?>
<?php echo HTMLHelper::_('uitab.endTabSet'); ?>
</div>
</div>
<input type="hidden" name="task" value="">
<?php echo HTMLHelper::_('form.token'); ?>
</form>
@@ -1,58 +0,0 @@
<?php
/**
* @package MokoJoomOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
$db = Factory::getDbo();
// Total published articles
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from('#__content')->where('state = 1'));
$totalArticles = (int) $db->loadResult();
// Articles with OG tags
$db->setQuery($db->getQuery(true)->select('COUNT(DISTINCT content_id)')->from('#__mokoog_tags')->where("content_type = 'com_content'")->where('published = 1'));
$articlesWithOg = (int) $db->loadResult();
// Articles missing OG data fields
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from('#__mokoog_tags')->where("content_type = 'com_content'")->where("og_title = ''")->where('published = 1'));
$missingTitle = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from('#__mokoog_tags')->where("content_type = 'com_content'")->where("og_description = ''")->where('published = 1'));
$missingDesc = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from('#__mokoog_tags')->where("content_type = 'com_content'")->where("og_image = ''")->where('published = 1'));
$missingImage = (int) $db->loadResult();
$coverage = $totalArticles > 0 ? round(($articlesWithOg / $totalArticles) * 100) : 0;
?>
<div class="mokoog-coverage card mb-3">
<div class="card-body">
<h4 class="card-title"><?php echo Text::_('COM_MOKOOG_COVERAGE_TITLE'); ?></h4>
<div class="row">
<div class="col-md-3 text-center">
<div class="display-4 <?php echo $coverage >= 80 ? 'text-success' : ($coverage >= 50 ? 'text-warning' : 'text-danger'); ?>">
<?php echo $coverage; ?>%
</div>
<small class="text-muted"><?php echo Text::_('COM_MOKOOG_COVERAGE_PERCENT'); ?></small>
</div>
<div class="col-md-9">
<ul class="list-unstyled">
<li><?php echo Text::sprintf('COM_MOKOOG_COVERAGE_ARTICLES', $articlesWithOg, $totalArticles); ?></li>
<li><?php echo Text::sprintf('COM_MOKOOG_COVERAGE_MISSING_TITLE', $missingTitle); ?></li>
<li><?php echo Text::sprintf('COM_MOKOOG_COVERAGE_MISSING_DESC', $missingDesc); ?></li>
<li><?php echo Text::sprintf('COM_MOKOOG_COVERAGE_MISSING_IMAGE', $missingImage); ?></li>
</ul>
</div>
</div>
</div>
</div>
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage com_mokoog * @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -21,7 +21,6 @@ use Joomla\CMS\Session\Session;
$token = Session::getFormToken(); $token = Session::getFormToken();
?> ?>
<?php include __DIR__ . '/coverage.php'; ?>
<form action="<?php echo Route::_('index.php?option=com_mokoog&view=tags'); ?>" method="post" name="adminForm" id="adminForm"> <form action="<?php echo Route::_('index.php?option=com_mokoog&view=tags'); ?>" method="post" name="adminForm" id="adminForm">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -85,7 +84,9 @@ $token = Session::getFormToken();
<?php echo (int) $item->content_id; ?> <?php echo (int) $item->content_id; ?>
</td> </td>
<td> <td>
<?php echo $this->escape($item->og_title ?: '(' . Text::_('COM_MOKOOG_AUTO_GENERATED') . ')'); ?> <a href="<?php echo Route::_('index.php?option=com_mokoog&task=tag.edit&id=' . (int) $item->id); ?>" title="<?php echo Text::_('JACTION_EDIT'); ?>">
<?php echo $this->escape($item->og_title ?: '(' . Text::_('COM_MOKOOG_AUTO_GENERATED') . ')'); ?>
</a>
</td> </td>
<td> <td>
<?php if ($item->og_image) : ?> <?php if ($item->og_image) : ?>
@@ -171,6 +172,23 @@ $token = Session::getFormToken();
</div> </div>
</div> </div>
<!-- CSV Import -->
<div id="mokoog-import-panel" style="display:none;" class="card mt-3">
<div class="card-body">
<h4><?php echo Text::_('COM_MOKOOG_TOOLBAR_IMPORT'); ?></h4>
<form action="<?php echo Route::_('index.php?option=com_mokoog&task=importexport.import'); ?>" method="post" enctype="multipart/form-data" class="mt-2">
<div class="mb-2">
<input type="file" name="jform[csv_file]" accept=".csv" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">
<span class="icon-upload" aria-hidden="true"></span>
<?php echo Text::_('COM_MOKOOG_TOOLBAR_IMPORT'); ?>
</button>
<?php echo HTMLHelper::_('form.token'); ?>
</form>
</div>
</div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Intercept the batch.generate toolbar button // Intercept the batch.generate toolbar button
@@ -180,6 +198,13 @@ document.addEventListener('DOMContentLoaded', function() {
mokoogBatchGenerate(); mokoogBatchGenerate();
return; return;
} }
if (task === 'mokoog.showimport') {
var ip = document.getElementById('mokoog-import-panel');
if (ip) {
ip.style.display = (ip.style.display === 'none' ? 'block' : 'none');
}
return;
}
if (origSubmitbutton) { if (origSubmitbutton) {
origSubmitbutton(task); origSubmitbutton(task);
} }
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage plg_content_mokoog * @subpackage plg_content_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -1,4 +1,4 @@
; MokoJoomOpenGraph - Content Plugin Language File ; MokoSuiteOpenGraph - Content Plugin Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
@@ -1,6 +1,6 @@
; MokoJoomOpenGraph - Content Plugin System Language File ; MokoSuiteOpenGraph - Content Plugin System Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
PLG_CONTENT_MOKOOG="Content - MokoJoomOpenGraph" PLG_CONTENT_MOKOOG="Content - MokoSuiteOpenGraph"
PLG_CONTENT_MOKOOG_DESCRIPTION="Adds Open Graph fields to article and menu item edit forms for per-page social sharing control." PLG_CONTENT_MOKOOG_DESCRIPTION="Adds Open Graph fields to article and menu item edit forms for per-page social sharing control."
@@ -1,4 +1,4 @@
; MokoJoomOpenGraph - Content Plugin Language File ; MokoSuiteOpenGraph - Content Plugin Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
@@ -1,6 +1,6 @@
; MokoJoomOpenGraph - Content Plugin System Language File ; MokoSuiteOpenGraph - Content Plugin System Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
PLG_CONTENT_MOKOOG="Content - MokoJoomOpenGraph" PLG_CONTENT_MOKOOG="Content - MokoSuiteOpenGraph"
PLG_CONTENT_MOKOOG_DESCRIPTION="Adds Open Graph fields to article and menu item edit forms for per-page social sharing control." PLG_CONTENT_MOKOOG_DESCRIPTION="Adds Open Graph fields to article and menu item edit forms for per-page social sharing control."
@@ -1,5 +1,5 @@
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage plg_content_mokoog * @subpackage plg_content_mokoog
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GPL-3.0-or-later * @license GPL-3.0-or-later
@@ -2,7 +2,7 @@
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json", "$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
"name": "plg_content_mokoog", "name": "plg_content_mokoog",
"version": "01.00.00", "version": "01.00.00",
"description": "MokoJoomOpenGraph Content Plugin Assets", "description": "MokoSuiteOpenGraph Content Plugin Assets",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"assets": [ "assets": [
{ {
@@ -1,5 +1,5 @@
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage plg_content_mokoog * @subpackage plg_content_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage plg_content_mokoog * @subpackage plg_content_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage plg_content_mokoog * @subpackage plg_content_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE * @license GNU General Public License version 3 or later; see LICENSE
--> -->
<extension type="plugin" group="content" method="upgrade"> <extension type="plugin" group="content" method="upgrade">
<name>Content - MokoJoomOpenGraph</name> <name>Content - MokoSuiteOpenGraph</name>
<version>01.04.11</version> <version>01.07.00</version>
<creationDate>2026-05-23</creationDate> <creationDate>2026-05-23</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage plg_content_mokoog * @subpackage plg_content_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @package MokoJoomOpenGraph * @package MokoSuiteOpenGraph
* @subpackage plg_content_mokoog * @subpackage plg_content_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech> * @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
@@ -187,7 +187,7 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
$contentType = $supportedContexts[$context]; $contentType = $supportedContexts[$context];
$contentId = (int) $article->id; $contentId = (int) $article->id;
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true) $query = $db->getQuery(true)
->delete($db->quoteName('#__mokoog_tags')) ->delete($db->quoteName('#__mokoog_tags'))
->where($db->quoteName('content_type') . ' = ' . $db->quote($contentType)) ->where($db->quoteName('content_type') . ' = ' . $db->quote($contentType))
@@ -208,7 +208,7 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
*/ */
private function loadOgData(string $contentType, int $contentId, string $language = '*'): ?object private function loadOgData(string $contentType, int $contentId, string $language = '*'): ?object
{ {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName([ ->select($db->quoteName([
'og_title', 'og_description', 'og_image', 'og_type', 'og_video', 'og_title', 'og_description', 'og_image', 'og_type', 'og_video',
@@ -239,7 +239,7 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
*/ */
private function saveOgData(string $contentType, int $contentId, array $ogData, string $language = '*'): void private function saveOgData(string $contentType, int $contentId, array $ogData, string $language = '*'): void
{ {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
// Check if record exists for this content + language // Check if record exists for this content + language
$query = $db->getQuery(true) $query = $db->getQuery(true)
@@ -274,7 +274,7 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
'seo_title' => strip_tags(trim($ogData['seo_title'] ?? '')), 'seo_title' => strip_tags(trim($ogData['seo_title'] ?? '')),
'meta_description' => strip_tags(trim($ogData['meta_description'] ?? '')), 'meta_description' => strip_tags(trim($ogData['meta_description'] ?? '')),
'robots' => trim($robots), 'robots' => trim($robots),
'canonical_url' => trim($ogData['canonical_url'] ?? ''), 'canonical_url' => $this->sanitizeUrl($ogData['canonical_url'] ?? ''),
'published' => 1, 'published' => 1,
'modified' => Factory::getDate()->toSql(), 'modified' => Factory::getDate()->toSql(),
]; ];
@@ -322,7 +322,14 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
{ {
$json = trim($json); $json = trim($json);
if ($json === '' || json_decode($json) === null) { if ($json === '') {
return '';
}
// Only accept JSON objects/arrays. Scalars (42, "x", true) decode to a
// non-null value but would crash the frontend renderer when treated as
// an array (writing $decoded['@context'] onto a scalar is a fatal error).
if (!\is_array(json_decode($json, true))) {
return ''; return '';
} }
@@ -1,4 +1,4 @@
; MokoJoomOpenGraph - System Plugin Language File ; MokoSuiteOpenGraph - System Plugin Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
@@ -1,6 +1,6 @@
; MokoJoomOpenGraph - System Plugin System Language File ; MokoSuiteOpenGraph - System Plugin System Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
PLG_SYSTEM_MOKOOG="System - MokoJoomOpenGraph" PLG_SYSTEM_MOKOOG="System - MokoSuiteOpenGraph"
PLG_SYSTEM_MOKOOG_DESCRIPTION="Injects Open Graph and Twitter Card meta tags into every page for optimal social media sharing previews." PLG_SYSTEM_MOKOOG_DESCRIPTION="Injects Open Graph and Twitter Card meta tags into every page for optimal social media sharing previews."
@@ -1,4 +1,4 @@
; MokoJoomOpenGraph - System Plugin Language File ; MokoSuiteOpenGraph - System Plugin Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
@@ -1,6 +1,6 @@
; MokoJoomOpenGraph - System Plugin System Language File ; MokoSuiteOpenGraph - System Plugin System Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved. ; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later ; License: GPL-3.0-or-later
PLG_SYSTEM_MOKOOG="System - MokoJoomOpenGraph" PLG_SYSTEM_MOKOOG="System - MokoSuiteOpenGraph"
PLG_SYSTEM_MOKOOG_DESCRIPTION="Injects Open Graph and Twitter Card meta tags into every page for optimal social media sharing previews." PLG_SYSTEM_MOKOOG_DESCRIPTION="Injects Open Graph and Twitter Card meta tags into every page for optimal social media sharing previews."

Some files were not shown because too many files have changed in this diff Show More