feat: integrate MokoJoomTOS as Offline Bypass feature plugin + add OpenGraph to catalog

New plugin: plg_system_mokowaas_tos (Offline Bypass)
- Keeps configured pages accessible during offline mode
- SEF path matching + Itemid fallback
- Custom MenuslugField for multi-select menu items
- Include children option for parent menu matching
- Renamed from MokoJoomTOS to MokoWaaS Offline Bypass

Also:
- Added MokoJoomOpenGraph to extension manager catalog
- Added to package manifest, script.php, dashboard, cascade list

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-06-02 14:52:06 -05:00
parent 9565911089
commit 1cdbfd035d
11 changed files with 369 additions and 0 deletions
@@ -45,6 +45,14 @@ class DashboardModel extends BaseDatabaseModel
'protected' => false,
'configure_only' => false,
],
'mokowaas_tos' => [
'icon' => 'icon-globe',
'category' => 'security',
'label' => 'Offline Bypass',
'description' => 'Keep selected pages (TOS, Privacy Policy) accessible during offline mode.',
'protected' => false,
'configure_only' => true,
],
'mokowaas_devtools' => [
'icon' => 'icon-wrench',
'category' => 'tools',
@@ -96,6 +96,16 @@ class ExtensionsModel extends BaseDatabaseModel
'article' => 'https://mokoconsulting.tech/kb/mokogallerycalendar',
'protected' => false,
],
'MokoJoomOpenGraph' => [
'label' => 'MokoJoomOpenGraph',
'description' => 'Open Graph meta tags for articles, categories, and pages. Controls Facebook, Twitter, and LinkedIn link previews.',
'element' => 'pkg_mokoog',
'type' => 'package',
'icon' => 'icon-share-alt',
'category' => 'Components',
'article' => 'https://mokoconsulting.tech/kb/mokojoomopengraph',
'protected' => false,
],
];
private const GITEA_URL = 'https://git.mokoconsulting.tech';
@@ -1424,6 +1424,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
$db->quote('mokowaas_firewall'),
$db->quote('mokowaas_tenant'),
$db->quote('mokowaas_devtools'),
$db->quote('mokowaas_tos'),
$db->quote('mod_mokowaas_cpanel'),
];
@@ -0,0 +1,13 @@
; MokoWaaS Terms of Service Plugin
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later
PLG_SYSTEM_MOKOWAAS_TOS="System - MokoWaaS Offline Bypass"
PLG_SYSTEM_MOKOWAAS_TOS_DESC="Keep selected pages (Terms of Service, Privacy Policy, etc.) accessible when the site is in offline mode."
PLG_SYSTEM_MOKOWAAS_TOS_FIELDSET_BASIC="Offline-Accessible Pages"
PLG_SYSTEM_MOKOWAAS_TOS_SLUG_LABEL="Menu Items to Keep Online"
PLG_SYSTEM_MOKOWAAS_TOS_SLUG_DESC="Select menu items that remain accessible during offline mode. Hold Ctrl/Cmd for multiple."
PLG_SYSTEM_MOKOWAAS_TOS_CHILDREN_LABEL="Include Child Menu Items"
PLG_SYSTEM_MOKOWAAS_TOS_CHILDREN_DESC="Also allow access to child pages under the selected items."
PLG_SYSTEM_MOKOWAAS_TOS_SEF_WARNING="SEF URLs are disabled - path matching requires SEF. Itemid fallback is active."
@@ -0,0 +1,3 @@
; MokoWaaS Terms of Service Plugin - System strings
PLG_SYSTEM_MOKOWAAS_TOS="System - MokoWaaS Offline Bypass"
PLG_SYSTEM_MOKOWAAS_TOS_DESC="Keep selected pages (Terms of Service, Privacy Policy, etc.) accessible when the site is in offline mode."
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoWaaS Offline Bypass</name>
<element>mokowaas_tos</element>
<author>Moko Consulting</author>
<creationDate>2026-06-02</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.32.00</version>
<description>PLG_SYSTEM_MOKOWAAS_TOS_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoWaaSTos</namespace>
<files>
<folder>src</folder>
<folder>services</folder>
<folder>language</folder>
</files>
<languages folder="language">
<language tag="en-GB">en-GB/plg_system_mokowaas_tos.ini</language>
<language tag="en-GB">en-GB/plg_system_mokowaas_tos.sys.ini</language>
</languages>
<config>
<fields name="params" addfieldprefix="Moko\Plugin\System\MokoWaaSTos\Field">
<fieldset name="basic" label="PLG_SYSTEM_MOKOWAAS_TOS_FIELDSET_BASIC">
<field name="tos_slug" type="menuslug"
label="PLG_SYSTEM_MOKOWAAS_TOS_SLUG_LABEL"
description="PLG_SYSTEM_MOKOWAAS_TOS_SLUG_DESC"
multiple="true" />
<field name="include_children" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_TOS_CHILDREN_LABEL"
description="PLG_SYSTEM_MOKOWAAS_TOS_CHILDREN_DESC"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
</fields>
</config>
</extension>
@@ -0,0 +1,34 @@
<?php
/**
* @package MokoWaaS
* @subpackage plg_system_mokowaas_tos
* @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\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Moko\Plugin\System\MokoWaaSTos\Extension\Tos;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$dispatcher = $container->get(DispatcherInterface::class);
$plugin = new Tos($dispatcher, (array) PluginHelper::getPlugin('system', 'mokowaas_tos'));
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
@@ -0,0 +1,172 @@
<?php
/**
* @package MokoWaaS
* @subpackage plg_system_mokowaas_tos
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Moko\Plugin\System\MokoWaaSTos\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Uri\Uri;
use Joomla\Event\SubscriberInterface;
/**
* MokoWaaS Terms of Service Plugin
*
* Allows configured menu items (Terms of Service, Privacy Policy, etc.)
* to remain accessible when the site is in offline mode.
*
* @since 02.32.00
*/
final class Tos extends CMSPlugin implements SubscriberInterface
{
protected $autoloadLanguage = true;
public static function getSubscribedEvents(): array
{
return [
'onAfterRoute' => 'onAfterRoute',
];
}
public function onAfterRoute(): void
{
$app = $this->getApplication();
if (!$app->isClient('site'))
{
return;
}
$config = $app->getConfig();
if (!$config->get('offline'))
{
return;
}
$slugs = $this->params->get('tos_slug', []);
if (\is_string($slugs))
{
$slugs = array_filter([trim($slugs)]);
}
else
{
$slugs = (array) $slugs;
}
if (empty($slugs))
{
return;
}
$includeChildren = (int) $this->params->get('include_children', 1);
if ($this->matchByPath($slugs, $config, $app, $includeChildren))
{
return;
}
$this->matchByItemId($slugs, $config, $app, $includeChildren);
}
private function matchByPath(array $slugs, $config, $app, int $includeChildren = 1): bool
{
$uri = Uri::getInstance();
$path = urldecode(trim($uri->getPath(), '/'));
$base = trim(Uri::base(true), '/');
if (!empty($base) && strpos($path, $base) === 0)
{
$path = trim(substr($path, \strlen($base)), '/');
}
if (empty($path) || $path === 'index.php')
{
return false;
}
foreach ($slugs as $slug)
{
$slug = trim((string) $slug);
if (empty($slug))
{
continue;
}
if ($path === $slug || ($includeChildren && strpos($path, $slug . '/') === 0))
{
$this->bypassOffline($config, $app);
return true;
}
}
return false;
}
private function matchByItemId(array $slugs, $config, $app, int $includeChildren = 1): bool
{
$itemId = (int) $app->getInput()->getInt('Itemid', 0);
if (!$itemId)
{
return false;
}
try
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('path'))
->from($db->quoteName('#__menu'))
->where($db->quoteName('id') . ' = ' . $itemId)
->where($db->quoteName('published') . ' = 1')
->where($db->quoteName('client_id') . ' = 0');
$db->setQuery($query);
$menuPath = trim((string) $db->loadResult(), '/');
if (empty($menuPath))
{
return false;
}
foreach ($slugs as $slug)
{
$slug = trim((string) $slug);
if (empty($slug))
{
continue;
}
if ($menuPath === $slug || ($includeChildren && strpos($menuPath, $slug . '/') === 0))
{
$this->bypassOffline($config, $app);
return true;
}
}
}
catch (\Throwable $e)
{
// Silent
}
return false;
}
private function bypassOffline($config, $app): void
{
$config->set('offline', 0);
$app->getInput()->set('tmpl', 'component');
}
}
@@ -0,0 +1,81 @@
<?php
/**
* @package MokoWaaS
* @subpackage plg_system_mokowaas_tos
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Moko\Plugin\System\MokoWaaSTos\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\Language\Text;
class MenuslugField extends ListField
{
protected $type = 'Menuslug';
protected function getOptions()
{
$options = parent::getOptions();
try
{
$sef = Factory::getApplication()->get('sef', true);
if (!$sef)
{
$options[] = (object) [
'value' => '',
'text' => Text::_('PLG_SYSTEM_MOKOWAAS_TOS_SEF_WARNING'),
'disabled' => true,
];
}
}
catch (\Throwable $e)
{
// Ignore
}
try
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName(['path', 'alias', 'title', 'menutype']))
->from($db->quoteName('#__menu'))
->where($db->quoteName('published') . ' = 1')
->where($db->quoteName('client_id') . ' = 0')
->where($db->quoteName('alias') . ' != ' . $db->quote(''))
->order($db->quoteName('menutype') . ', ' . $db->quoteName('title'));
$db->setQuery($query);
$menuItems = $db->loadObjectList();
$lastMenuType = '';
foreach ($menuItems ?: [] as $item)
{
if ($item->menutype !== $lastMenuType)
{
if ($lastMenuType !== '')
{
$options[] = (object) ['value' => '', 'text' => '──────────────', 'disabled' => true];
}
$lastMenuType = $item->menutype;
}
$label = $item->title !== '' ? $item->title : ucwords(str_replace(['-', '_'], ' ', $item->alias));
$options[] = (object) ['value' => $item->path, 'text' => $label . ' (/' . $item->path . ')'];
}
}
catch (\Throwable $e)
{
// Silent
}
return $options;
}
}
+1
View File
@@ -17,6 +17,7 @@
<file type="plugin" id="plg_system_mokowaas_firewall" group="system">plg_system_mokowaas_firewall.zip</file>
<file type="plugin" id="plg_system_mokowaas_tenant" group="system">plg_system_mokowaas_tenant.zip</file>
<file type="plugin" id="plg_system_mokowaas_devtools" group="system">plg_system_mokowaas_devtools.zip</file>
<file type="plugin" id="plg_system_mokowaas_tos" group="system">plg_system_mokowaas_tos.zip</file>
<file type="component" id="com_mokowaas">com_mokowaas.zip</file>
<file type="module" id="mod_mokowaas_cpanel" client="administrator">mod_mokowaas_cpanel.zip</file>
<file type="plugin" id="plg_webservices_mokowaas" group="webservices">plg_webservices_mokowaas.zip</file>
+2
View File
@@ -42,6 +42,7 @@ class Pkg_MokowaasInstallerScript
$this->enablePlugin('system', 'mokowaas_firewall');
$this->enablePlugin('system', 'mokowaas_tenant');
$this->enablePlugin('system', 'mokowaas_devtools');
$this->enablePlugin('system', 'mokowaas_tos');
$this->enablePlugin('webservices', 'mokowaas');
$this->enablePlugin('task', 'mokowaasdemo');
$this->enablePlugin('task', 'mokowaassync');
@@ -289,6 +290,7 @@ class Pkg_MokowaasInstallerScript
$db->quote('mokowaas_firewall'),
$db->quote('mokowaas_tenant'),
$db->quote('mokowaas_devtools'),
$db->quote('mokowaas_tos'),
$db->quote('com_mokowaas'),
$db->quote('mod_mokowaas_cpanel'),
$db->quote('mokowaasdemo'),