diff --git a/source/packages/com_mokoog/src/Controller/TagController.php b/source/packages/com_mokoog/src/Controller/TagController.php
new file mode 100644
index 0000000..c1ce226
--- /dev/null
+++ b/source/packages/com_mokoog/src/Controller/TagController.php
@@ -0,0 +1,31 @@
+
+ * @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';
+}
diff --git a/source/packages/com_mokoog/src/View/Tag/HtmlView.php b/source/packages/com_mokoog/src/View/Tag/HtmlView.php
new file mode 100644
index 0000000..8b5d31a
--- /dev/null
+++ b/source/packages/com_mokoog/src/View/Tag/HtmlView.php
@@ -0,0 +1,76 @@
+
+ * @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');
+ }
+}
diff --git a/source/packages/com_mokoog/src/View/Tags/HtmlView.php b/source/packages/com_mokoog/src/View/Tags/HtmlView.php
index 9a07a00..e80d0ae 100644
--- a/source/packages/com_mokoog/src/View/Tags/HtmlView.php
+++ b/source/packages/com_mokoog/src/View/Tags/HtmlView.php
@@ -81,6 +81,8 @@ class HtmlView extends BaseHtmlView
protected function addToolbar(): void
{
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('importexport.export', 'download', '', 'COM_MOKOOG_TOOLBAR_EXPORT', false);
ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'tags.delete');
diff --git a/source/packages/com_mokoog/tmpl/tag/edit.php b/source/packages/com_mokoog/tmpl/tag/edit.php
new file mode 100644
index 0000000..63bfe41
--- /dev/null
+++ b/source/packages/com_mokoog/tmpl/tag/edit.php
@@ -0,0 +1,41 @@
+
+ * @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');
+?>
+
diff --git a/source/packages/com_mokoog/tmpl/tags/default.php b/source/packages/com_mokoog/tmpl/tags/default.php
index f3a3ad0..bf6a82b 100644
--- a/source/packages/com_mokoog/tmpl/tags/default.php
+++ b/source/packages/com_mokoog/tmpl/tags/default.php
@@ -85,7 +85,9 @@ $token = Session::getFormToken();
content_id; ?>
|
- escape($item->og_title ?: '(' . Text::_('COM_MOKOOG_AUTO_GENERATED') . ')'); ?>
+
+ escape($item->og_title ?: '(' . Text::_('COM_MOKOOG_AUTO_GENERATED') . ')'); ?>
+
|
og_image) : ?>
diff --git a/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php b/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php
index 91550d7..c7a792b 100644
--- a/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php
+++ b/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php
@@ -322,7 +322,14 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface
{
$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 '';
}
diff --git a/source/packages/plg_system_mokoog/src/Extension/MokoOG.php b/source/packages/plg_system_mokoog/src/Extension/MokoOG.php
index b472a9a..fc75984 100644
--- a/source/packages/plg_system_mokoog/src/Extension/MokoOG.php
+++ b/source/packages/plg_system_mokoog/src/Extension/MokoOG.php
@@ -358,7 +358,9 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
if (!empty($customSchema)) {
$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'])) {
$decoded['@context'] = 'https://schema.org';
}
|