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
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.
This commit is contained in:
@@ -16,13 +16,15 @@
|
||||
name="content_type"
|
||||
type="text"
|
||||
label="COM_MOKOOG_FIELD_CONTENT_TYPE"
|
||||
readonly="true"
|
||||
description="COM_MOKOOG_FIELD_CONTENT_TYPE_DESC"
|
||||
required="true"
|
||||
/>
|
||||
<field
|
||||
name="content_id"
|
||||
type="number"
|
||||
label="COM_MOKOOG_FIELD_CONTENT_ID"
|
||||
readonly="true"
|
||||
description="COM_MOKOOG_FIELD_CONTENT_ID_DESC"
|
||||
required="true"
|
||||
/>
|
||||
<field
|
||||
name="og_title"
|
||||
@@ -77,37 +79,45 @@
|
||||
<option value="1">JPUBLISHED</option>
|
||||
<option value="0">JUNPUBLISHED</option>
|
||||
</field>
|
||||
<field
|
||||
name="language"
|
||||
type="contentlanguage"
|
||||
label="JFIELD_LANGUAGE_LABEL"
|
||||
default="*"
|
||||
>
|
||||
<option value="*">JALL</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
<fieldset name="seo" label="SEO Meta Tags">
|
||||
<fieldset name="seo" label="COM_MOKOOG_FIELDSET_SEO">
|
||||
<field
|
||||
name="seo_title"
|
||||
type="text"
|
||||
label="PLG_CONTENT_MOKOOG_FIELD_SEO_TITLE"
|
||||
description="PLG_CONTENT_MOKOOG_FIELD_SEO_TITLE_DESC"
|
||||
label="COM_MOKOOG_FIELD_SEO_TITLE"
|
||||
description="COM_MOKOOG_FIELD_SEO_TITLE_DESC"
|
||||
filter="string"
|
||||
maxlength="255"
|
||||
maxlength="70"
|
||||
/>
|
||||
<field
|
||||
name="meta_description"
|
||||
type="textarea"
|
||||
label="PLG_CONTENT_MOKOOG_FIELD_META_DESCRIPTION"
|
||||
description="PLG_CONTENT_MOKOOG_FIELD_META_DESCRIPTION_DESC"
|
||||
label="COM_MOKOOG_FIELD_META_DESCRIPTION"
|
||||
description="COM_MOKOOG_FIELD_META_DESCRIPTION_DESC"
|
||||
filter="string"
|
||||
rows="3"
|
||||
maxlength="255"
|
||||
maxlength="200"
|
||||
/>
|
||||
<field
|
||||
name="robots"
|
||||
type="text"
|
||||
label="PLG_CONTENT_MOKOOG_FIELD_ROBOTS"
|
||||
description="PLG_CONTENT_MOKOOG_FIELD_ROBOTS_DESC"
|
||||
label="COM_MOKOOG_FIELD_ROBOTS"
|
||||
description="COM_MOKOOG_FIELD_ROBOTS_DESC"
|
||||
filter="string"
|
||||
/>
|
||||
<field
|
||||
name="canonical_url"
|
||||
type="url"
|
||||
label="PLG_CONTENT_MOKOOG_FIELD_CANONICAL_URL"
|
||||
description="PLG_CONTENT_MOKOOG_FIELD_CANONICAL_URL_DESC"
|
||||
label="COM_MOKOOG_FIELD_CANONICAL_URL"
|
||||
description="COM_MOKOOG_FIELD_CANONICAL_URL_DESC"
|
||||
filter="url"
|
||||
/>
|
||||
</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_DESC="%d tags missing custom description"
|
||||
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 <title> 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_DESC="%d tags missing custom description"
|
||||
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 <title> 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)."
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
<folder>View</folder>
|
||||
</files>
|
||||
<files folder="tmpl">
|
||||
<folder>tag</folder>
|
||||
<folder>tags</folder>
|
||||
</files>
|
||||
<files folder="sql">
|
||||
|
||||
@@ -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
|
||||
{
|
||||
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');
|
||||
|
||||
@@ -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>
|
||||
@@ -85,7 +85,9 @@ $token = Session::getFormToken();
|
||||
<?php echo (int) $item->content_id; ?>
|
||||
</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>
|
||||
<?php if ($item->og_image) : ?>
|
||||
|
||||
@@ -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 '';
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user