Compare commits

...

11 Commits

Author SHA1 Message Date
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
gitea-actions[bot] 6d4284c6c9 chore(version): pre-release bump to 01.05.02-dev [skip ci] 2026-06-28 19:51:06 +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
33 changed files with 266 additions and 57 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation # INGROUP: mokocli.Automation
# VERSION: 01.05.00 # VERSION: 01.06.03
# 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"
+1 -1
View File
@@ -5,7 +5,7 @@
## [01.05.00] --- 2026-06-28 ## [01.05.00] --- 2026-06-28
<!-- VERSION: 01.05.00 --> <!-- VERSION: 01.06.03 -->
All notable changes to MokoSuiteOpenGraph will be documented in this file. All notable changes to MokoSuiteOpenGraph will be documented in this file.
+1 -1
View File
@@ -14,7 +14,7 @@
DEFGROUP: Template-Joomla DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation INGROUP: Template-Joomla.Documentation
REPO: https://github.com/mokoconsulting-tech/Template-Joomla/ REPO: https://github.com/mokoconsulting-tech/Template-Joomla/
VERSION: 01.05.00 VERSION: 01.06.03
PATH: ./CODE_OF_CONDUCT.md PATH: ./CODE_OF_CONDUCT.md
BRIEF: Community expectations and enforcement guidelines BRIEF: Community expectations and enforcement guidelines
NOTE: Adapted with attribution from the Contributor Covenant v2.1 NOTE: Adapted with attribution from the Contributor Covenant v2.1
+1 -1
View File
@@ -19,7 +19,7 @@
DEFGROUP: mokoconsulting-tech.Template-Joomla DEFGROUP: mokoconsulting-tech.Template-Joomla
INGROUP: MokoStandards.Governance INGROUP: MokoStandards.Governance
REPO: https://github.com/mokoconsulting-tech/Template-Joomla REPO: https://github.com/mokoconsulting-tech/Template-Joomla
VERSION: 01.05.00 VERSION: 01.06.03
PATH: /GOVERNANCE.md PATH: /GOVERNANCE.md
BRIEF: Project governance rules, roles, and decision process for Template-Joomla BRIEF: Project governance rules, roles, and decision process for Template-Joomla
--> -->
+1 -1
View File
@@ -1,6 +1,6 @@
# MokoSuiteOpenGraph # MokoSuiteOpenGraph
<!-- VERSION: 01.05.00 --> <!-- VERSION: 01.06.03 -->
Open Graph, Twitter Card, and social sharing meta tag management for Joomla 6 and higher. Open Graph, Twitter Card, and social sharing meta tag management for Joomla 6 and higher.
+1 -1
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.05.00 VERSION: 01.06.03
BRIEF: Security vulnerability reporting and handling policy BRIEF: Security vulnerability reporting and handling policy
--> -->
@@ -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'))
+23 -13
View File
@@ -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>
@@ -66,3 +66,19 @@ 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)."
@@ -66,3 +66,19 @@ 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)."
+2 -1
View File
@@ -8,7 +8,7 @@
--> -->
<extension type="component" method="upgrade"> <extension type="component" method="upgrade">
<name>com_mokoog</name> <name>com_mokoog</name>
<version>01.05.00</version> <version>01.06.03</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>
@@ -50,6 +50,7 @@
<folder>View</folder> <folder>View</folder>
</files> </files>
<files folder="tmpl"> <files folder="tmpl">
<folder>tag</folder>
<folder>tags</folder> <folder>tags</folder>
</files> </files>
<files folder="sql"> <files folder="sql">
@@ -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.02 — no schema changes */
@@ -0,0 +1 @@
/* 01.06.03 — no schema changes */
@@ -27,13 +27,13 @@ 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')) { if (!Factory::getApplication()->getIdentity()->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,7 +60,7 @@ 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')) { if (!Factory::getApplication()->getIdentity()->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);
@@ -69,7 +69,7 @@ class BatchController extends BaseController
$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',
@@ -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)
@@ -102,7 +102,7 @@ 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();
@@ -161,7 +161,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;
@@ -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';
}
@@ -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');
}
}
@@ -81,6 +81,8 @@ 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::deleteList('JGLOBAL_CONFIRM_DELETE', 'tags.delete'); ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'tags.delete');
@@ -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>
@@ -13,7 +13,7 @@ defined('_JEXEC') or die;
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text; use Joomla\CMS\Language\Text;
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
// Total published articles // Total published articles
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from('#__content')->where('state = 1')); $db->setQuery($db->getQuery(true)->select('COUNT(*)')->from('#__content')->where('state = 1'));
@@ -85,7 +85,9 @@ $token = Session::getFormToken();
<?php echo (int) $item->content_id; ?> <?php echo (int) $item->content_id; ?>
</td> </td>
<td> <td>
<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') . ')'); ?> <?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) : ?>
@@ -8,7 +8,7 @@
--> -->
<extension type="plugin" group="content" method="upgrade"> <extension type="plugin" group="content" method="upgrade">
<name>Content - MokoSuiteOpenGraph</name> <name>Content - MokoSuiteOpenGraph</name>
<version>01.05.00</version> <version>01.06.03</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>
@@ -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)
@@ -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 -1
View File
@@ -8,7 +8,7 @@
--> -->
<extension type="plugin" group="system" method="upgrade"> <extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteOpenGraph</name> <name>System - MokoSuiteOpenGraph</name>
<version>01.05.00</version> <version>01.06.03</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>
@@ -241,7 +241,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
// Pinterest article:tag rich pins (from Joomla content tags) // Pinterest article:tag rich pins (from Joomla content tags)
if ($option === 'com_content' && $view === 'article' && $id > 0) { if ($option === 'com_content' && $view === 'article' && $id > 0) {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$tagQuery = $db->getQuery(true) $tagQuery = $db->getQuery(true)
->select($db->quoteName('t.title')) ->select($db->quoteName('t.title'))
->from($db->quoteName('#__tags', 't')) ->from($db->quoteName('#__tags', 't'))
@@ -358,7 +358,9 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
if (!empty($customSchema)) { if (!empty($customSchema)) {
$decoded = json_decode($customSchema, true); $decoded = json_decode($customSchema, true);
if ($decoded) { // Guard against scalar/invalid payloads — only arrays/objects are
// valid JSON-LD. Writing an array offset onto a scalar is fatal.
if (\is_array($decoded) && $decoded !== []) {
if (empty($decoded['@context'])) { if (empty($decoded['@context'])) {
$decoded['@context'] = 'https://schema.org'; $decoded['@context'] = 'https://schema.org';
} }
@@ -467,7 +469,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
return $this->loadOgDataByMenu((int) $menuItem->id) ?: $empty; return $this->loadOgDataByMenu((int) $menuItem->id) ?: $empty;
} }
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('*') ->select('*')
->from($db->quoteName('#__mokoog_tags')) ->from($db->quoteName('#__mokoog_tags'))
@@ -493,7 +495,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
*/ */
private function loadOgDataByType(string $contentType, int $contentId): ?object private function loadOgDataByType(string $contentType, int $contentId): ?object
{ {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$lang = Factory::getLanguage()->getTag(); $lang = Factory::getLanguage()->getTag();
$query = $db->getQuery(true) $query = $db->getQuery(true)
@@ -520,7 +522,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
*/ */
private function loadOgDataByMenu(int $menuId): ?object private function loadOgDataByMenu(int $menuId): ?object
{ {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$lang = Factory::getLanguage()->getTag(); $lang = Factory::getLanguage()->getTag();
$query = $db->getQuery(true) $query = $db->getQuery(true)
@@ -615,7 +617,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
// Fallback: check the article's category for an image // Fallback: check the article's category for an image
if ($view === 'article') { if ($view === 'article') {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$catQuery = $db->getQuery(true) $catQuery = $db->getQuery(true)
->select($db->quoteName('cat.params')) ->select($db->quoteName('cat.params'))
->from($db->quoteName('#__categories', 'cat')) ->from($db->quoteName('#__categories', 'cat'))
@@ -674,7 +676,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
return $cache[$id]; return $cache[$id];
} }
$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([
'a.title', 'a.introtext', 'a.fulltext', 'a.images', 'a.title', 'a.introtext', 'a.fulltext', 'a.images',
@@ -947,20 +949,20 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
*/ */
private function warnMissingLicenseKey(): void private function warnMissingLicenseKey(): void
{ {
$session = Factory::getSession(); $session = Factory::getApplication()->getSession();
if ($session->get('mokoog.license_warned', false)) { if ($session->get('mokoog.license_warned', false)) {
return; return;
} }
$user = Factory::getUser(); $user = Factory::getApplication()->getIdentity();
if ($user->guest || !$user->authorise('core.manage')) { if ($user->guest || !$user->authorise('core.manage')) {
return; return;
} }
try { try {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select($db->quoteName('extra_query')) ->select($db->quoteName('extra_query'))
@@ -1011,7 +1013,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
} }
try { try {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('p.id, p.sku, p.price, p.currency, p.stock_qty') ->select('p.id, p.sku, p.price, p.currency, p.stock_qty')
->select('c.title AS name, c.introtext AS description, c.images') ->select('c.title AS name, c.introtext AS description, c.images')
@@ -37,7 +37,7 @@ class JsonLdBuilder
$article = $cachedArticle; $article = $cachedArticle;
if (!$article) { if (!$article) {
$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([
'a.created', 'a.modified', 'a.publish_up', 'a.created', 'a.modified', 'a.publish_up',
@@ -179,7 +179,7 @@ class JsonLdBuilder
$product = $cachedProduct; $product = $cachedProduct;
if (!$product) { if (!$product) {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true) $query = $db->getQuery(true)
->select('p.sku, p.price, p.currency, p.stock_qty') ->select('p.sku, p.price, p.currency, p.stock_qty')
->from($db->quoteName('#__mokosuite_crm_products', 'p')) ->from($db->quoteName('#__mokosuite_crm_products', 'p'))
@@ -35,7 +35,7 @@ class SitemapBuilder
$allowed = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never']; $allowed = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];
$changefreq = \in_array($changefreq, $allowed, true) ? $changefreq : 'weekly'; $changefreq = \in_array($changefreq, $allowed, true) ? $changefreq : 'weekly';
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
// Get all published articles // Get all published articles
$query = $db->getQuery(true) $query = $db->getQuery(true)
@@ -8,7 +8,7 @@
--> -->
<extension type="plugin" group="webservices" method="upgrade"> <extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteOpenGraph</name> <name>Web Services - MokoSuiteOpenGraph</name>
<version>01.05.00</version> <version>01.06.03</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 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade"> <extension type="package" method="upgrade">
<name>Package - MokoSuiteOpenGraph</name> <name>Package - MokoSuiteOpenGraph</name>
<packagename>mokoog</packagename> <packagename>mokoog</packagename>
<version>01.05.00</version> <version>01.06.03</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>
+4 -4
View File
@@ -55,7 +55,7 @@ class Pkg_MokoOGInstallerScript
if ($type === 'install') if ($type === 'install')
{ {
$db = Factory::getDbo(); $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
foreach (['system', 'content', 'webservices'] as $folder) foreach (['system', 'content', 'webservices'] as $folder)
{ {
@@ -79,7 +79,7 @@ class Pkg_MokoOGInstallerScript
{ {
try try
{ {
$db = \Joomla\CMS\Factory::getDbo(); $db = \Joomla\CMS\Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select($db->quoteName('us.extra_query')) ->select($db->quoteName('us.extra_query'))
@@ -103,7 +103,7 @@ class Pkg_MokoOGInstallerScript
try try
{ {
$db = \Joomla\CMS\Factory::getDbo(); $db = \Joomla\CMS\Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select($db->quoteName('us.update_site_id')) ->select($db->quoteName('us.update_site_id'))
@@ -133,7 +133,7 @@ class Pkg_MokoOGInstallerScript
{ {
try try
{ {
$db = \Joomla\CMS\Factory::getDbo(); $db = \Joomla\CMS\Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$db->setQuery( $db->setQuery(
$db->getQuery(true) $db->getQuery(true)
->select([$db->quoteName('update_site_id'), $db->quoteName('extra_query')]) ->select([$db->quoteName('update_site_id'), $db->quoteName('extra_query')])