feat: include children toggle, manifest fix, and bug fixes (#88)
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Release configuration (push) Failing after 5s
Generic: Repo Health / Repository health (push) Failing after 5s
Generic: Repo Health / Scripts governance (push) Successful in 6s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Release configuration (push) Failing after 5s
Generic: Repo Health / Repository health (push) Failing after 5s
Generic: Repo Health / Scripts governance (push) Successful in 6s
feat: include children toggle, manifest fix, and bug fixes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #88.
This commit is contained in:
+45
-106
@@ -21,8 +21,8 @@
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: MokoJoomTOS
|
||||
INGROUP: plg_system_mokojoomtos
|
||||
REPO: https://github.com/mokoconsulting-tech/MokoJoomTOS
|
||||
VERSION: 04.01.00
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS
|
||||
VERSION: 04.02.01
|
||||
PATH: ./CHANGELOG.md
|
||||
BRIEF: Version history and release notes
|
||||
-->
|
||||
@@ -36,6 +36,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- enablePlugin() now called unconditionally in postflight() (#89)
|
||||
- Replaced Table::addIncludePath() with bootComponent()->getMVCFactory() for Joomla 5 (#90)
|
||||
- Added non-SEF URL fallback via Itemid matching (#91)
|
||||
- enablePlugin() now fires on upgrade path (#92)
|
||||
- Removed $_GET superglobal mutation (#93)
|
||||
- Added params, metadata, attribs defaults to article creation (#94)
|
||||
- Applied urldecode() to URI path before slug comparison (#95)
|
||||
- Cast Registry return to array before iterating slugs (#96)
|
||||
- Fixed MenuslugField separator `disable` to `disabled` property (#99)
|
||||
|
||||
### Added
|
||||
|
||||
- SEF disabled warning in MenuslugField dropdown (#97)
|
||||
- Include Children toggle for offline-accessible menu items (defaults to Yes)
|
||||
|
||||
### Fixed (Manifest)
|
||||
|
||||
- Hardcode description in XML manifest (language variables don't resolve during install)
|
||||
|
||||
### Changed
|
||||
|
||||
- Stripped legacy mokojoomtos.php to minimal stub (#101)
|
||||
- Converted script.php indentation from spaces to tabs (#102)
|
||||
- Renamed installer class to PlgSystemMokojoomtosInstallerScript (#103)
|
||||
|
||||
## [04.01.00] - 2026-05-16
|
||||
|
||||
### Fixed
|
||||
@@ -55,7 +82,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Match against full menu route path instead of alias only (fixes nested routes like `/legal/terms-of-service`)
|
||||
- Plugin pretty name updated to Joomla convention: "System - Moko Terms of Service"
|
||||
- Renamed `.mokogitea/` to `.gitea/` for standard Gitea compatibility
|
||||
- MenuslugField now stores and displays full route paths (e.g., `legal/terms-of-service`)
|
||||
|
||||
### Removed
|
||||
@@ -77,119 +103,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
|
||||
- Added Gitea update server URL to plugin manifest (`updates.xml` on `main`)
|
||||
- Removed obsolete `src/plugins/` legacy directory
|
||||
- Fixed template chrome loading issue by changing event hook from onAfterInitialise to onAfterRoute
|
||||
- Component-only view now properly applied when site is offline
|
||||
|
||||
### Removed
|
||||
|
||||
- Legacy duplicate manifest at `src/plugins/system/mokojoomtos/mokojoomtos.xml`
|
||||
|
||||
## [03.09.00] - 2026-02-28
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated version number to 03.09.00 across all files
|
||||
- Fixed template chrome loading issue by changing event hook from onAfterInitialise to onAfterRoute
|
||||
|
||||
### Fixed
|
||||
|
||||
- Template chrome (header, footer, modules) no longer loads when accessing TOS page in offline mode
|
||||
- Component-only view now properly applied when site is offline
|
||||
|
||||
## [1.0.0] - 2026-01-16
|
||||
|
||||
### Added
|
||||
|
||||
- Initial template repository structure
|
||||
- Comprehensive Makefile with 30+ build targets
|
||||
- Complete README.md with usage documentation
|
||||
- CONTRIBUTING.md with contribution guidelines
|
||||
- SECURITY.md with security policy and best practices
|
||||
- CHANGELOG.md for version tracking
|
||||
- LICENSE file (GPL-3.0-or-later)
|
||||
- EditorConfig for consistent code formatting
|
||||
- Documentation structure in `docs/` directory
|
||||
- Placeholders for component directories (`admin/`, `site/`, `media/`)
|
||||
- MokoStandards compliance:
|
||||
- File header standards with copyright and SPDX identifiers
|
||||
- Joomla coding standards configuration
|
||||
- PHPStan static analysis setup
|
||||
- Dependency security auditing
|
||||
- Makefile targets for:
|
||||
- Dependency management (install-deps, update-deps)
|
||||
- Code validation (lint, phpcs, phpstan)
|
||||
- Testing (test, test-coverage)
|
||||
- Building (build, build-assets)
|
||||
- Development (dev-install, watch-assets)
|
||||
- Release management (release, bump-version)
|
||||
- Utility commands (clean, version, help)
|
||||
- Documentation index files with MokoStandards metadata
|
||||
|
||||
### Documentation
|
||||
|
||||
- Comprehensive README with:
|
||||
- Quick start guide
|
||||
- Prerequisites and installation
|
||||
- Usage examples
|
||||
- Project structure overview
|
||||
- Makefile command reference
|
||||
- Standards compliance information
|
||||
- Detailed CONTRIBUTING guide with:
|
||||
- Commit message conventions
|
||||
- DCO sign-off requirements
|
||||
- Development setup instructions
|
||||
- Code review process
|
||||
- Security policy with:
|
||||
- Vulnerability reporting procedures
|
||||
- Security best practices
|
||||
- Code examples for secure development
|
||||
- Template structure for Joomla 4.x and 5.x compatibility
|
||||
|
||||
## Version Guidelines
|
||||
|
||||
### Version Numbering
|
||||
|
||||
This template follows [Semantic Versioning](https://semver.org/):
|
||||
|
||||
- **MAJOR** version for incompatible changes (e.g., 2.0.0)
|
||||
- **MINOR** version for new features (e.g., 1.1.0)
|
||||
- **PATCH** version for bug fixes (e.g., 1.0.1)
|
||||
|
||||
### Change Categories
|
||||
|
||||
- **Added**: New features
|
||||
- **Changed**: Changes to existing functionality
|
||||
- **Deprecated**: Soon-to-be removed features
|
||||
- **Removed**: Removed features
|
||||
- **Fixed**: Bug fixes
|
||||
- **Security**: Security fixes
|
||||
|
||||
## Release Notes
|
||||
|
||||
### [1.0.0] Release Notes
|
||||
|
||||
This is the initial release of the MokoJoomTOS plugin. It provides a production-ready Joomla system plugin for offline TOS access with:
|
||||
|
||||
- **Complete build system** via comprehensive Makefile
|
||||
- **MokoStandards compliance** out of the box
|
||||
- **Developer-friendly** workflow with automation
|
||||
- **Security-focused** with built-in best practices
|
||||
- **Well-documented** with clear usage instructions
|
||||
|
||||
This template is ready for use in creating new Joomla components that follow organizational coding standards and best practices.
|
||||
|
||||
### Migration Guide
|
||||
|
||||
**For New Projects**: Simply use this template to create your new component repository.
|
||||
|
||||
**For Existing Projects**: Review the Makefile, documentation structure, and standards compliance files to gradually adopt features that benefit your project.
|
||||
- Initial plugin release
|
||||
- Offline mode bypass for configured menu slug
|
||||
- Auto-provisioning installer (creates article, menu type, menu item)
|
||||
- Component-only rendering (`tmpl=component`)
|
||||
- Language files for en-GB and en-US
|
||||
- MokoStandards compliance
|
||||
|
||||
## Links
|
||||
|
||||
- [Repository](https://github.com/mokoconsulting-tech/MokoJoomTOS)
|
||||
- [Issues](https://github.com/mokoconsulting-tech/MokoJoomTOS/issues)
|
||||
- [Pull Requests](https://github.com/mokoconsulting-tech/MokoJoomTOS/pulls)
|
||||
- [MokoStandards](https://github.com/mokoconsulting-tech/MokoCodingDefaults)
|
||||
- [Repository](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS)
|
||||
- [Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/issues)
|
||||
- [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/releases)
|
||||
|
||||
[Unreleased]: https://github.com/mokoconsulting-tech/MokoJoomTOS/compare/v03.09.00...HEAD
|
||||
[03.09.00]: https://github.com/mokoconsulting-tech/MokoJoomTOS/releases/tag/v03.09.00
|
||||
[1.0.0]: https://github.com/mokoconsulting-tech/MokoJoomTOS/releases/tag/v1.0.0
|
||||
[Unreleased]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/compare/stable...dev
|
||||
[04.01.00]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/releases/tag/stable
|
||||
[04.00.00]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/compare/v03.09.00...stable
|
||||
[03.09.00]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/releases/tag/v03.09.00
|
||||
[1.0.0]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/releases/tag/v1.0.0
|
||||
|
||||
@@ -11,10 +11,16 @@ PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Offline-Accessible Menu Items"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Select one or more menu items that should remain accessible when the site is in offline mode. Hold Ctrl/Cmd to select multiple items."
|
||||
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_LABEL="Include Child Menu Items"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_DESC="When enabled, child menu items under the selected items will also be accessible during offline mode. For example, selecting 'legal' will also allow access to 'legal/terms-of-service' and 'legal/privacy-policy'."
|
||||
|
||||
; Help
|
||||
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="<strong>Step 1:</strong> Create articles for your legal pages (Terms of Service, Privacy Policy, etc.).<br/><strong>Step 2:</strong> Create menu items pointing to those articles.<br/><strong>Step 3:</strong> Select the menu items above (hold Ctrl/Cmd to select multiple).<br/><strong>Step 4:</strong> When your site goes offline, visitors can still access the selected pages.<br/><br/><em>Tip:</em> The dropdown shows the full URL path for each menu item (e.g., /legal/terms-of-service)."
|
||||
|
||||
; Warnings
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_SEF_WARNING="⚠ SEF URLs are disabled — path matching requires SEF. Itemid fallback is active."
|
||||
|
||||
; Errors
|
||||
PLG_SYSTEM_MOKOJOOMTOS_ERROR_LOADING_MENU_ITEMS="Error loading menu items: %s"
|
||||
|
||||
|
||||
@@ -11,10 +11,16 @@ PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Offline-Accessible Menu Items"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Select one or more menu items that should remain accessible when the site is in offline mode. Hold Ctrl/Cmd to select multiple items."
|
||||
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_LABEL="Include Child Menu Items"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_DESC="When enabled, child menu items under the selected items will also be accessible during offline mode. For example, selecting 'legal' will also allow access to 'legal/terms-of-service' and 'legal/privacy-policy'."
|
||||
|
||||
; Help
|
||||
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="<strong>Step 1:</strong> Create articles for your legal pages (Terms of Service, Privacy Policy, etc.).<br/><strong>Step 2:</strong> Create menu items pointing to those articles.<br/><strong>Step 3:</strong> Select the menu items above (hold Ctrl/Cmd to select multiple).<br/><strong>Step 4:</strong> When your site goes offline, visitors can still access the selected pages.<br/><br/><em>Tip:</em> The dropdown shows the full URL path for each menu item (e.g., /legal/terms-of-service)."
|
||||
|
||||
; Warnings
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_SEF_WARNING="⚠ SEF URLs are disabled — path matching requires SEF. Itemid fallback is active."
|
||||
|
||||
; Errors
|
||||
PLG_SYSTEM_MOKOJOOMTOS_ERROR_LOADING_MENU_ITEMS="Error loading menu items: %s"
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Offline-Accessible Menu Items"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Select one or more menu items that should remain accessible when the site is in offline mode. Hold Ctrl/Cmd to select multiple items."
|
||||
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_LABEL="Include Child Menu Items"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_DESC="When enabled, child menu items under the selected items will also be accessible during offline mode. For example, selecting 'legal' will also allow access to 'legal/terms-of-service' and 'legal/privacy-policy'."
|
||||
|
||||
; Help
|
||||
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="<strong>Step 1:</strong> Create articles for your legal pages (Terms of Service, Privacy Policy, etc.).<br/><strong>Step 2:</strong> Create menu items pointing to those articles.<br/><strong>Step 3:</strong> Select the menu items above (hold Ctrl/Cmd to select multiple).<br/><strong>Step 4:</strong> When your site goes offline, visitors can still access the selected pages.<br/><br/><em>Tip:</em> The dropdown shows the full URL path for each menu item (e.g., /legal/terms-of-service)."
|
||||
|
||||
@@ -11,6 +11,9 @@ PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Offline-Accessible Menu Items"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Select one or more menu items that should remain accessible when the site is in offline mode. Hold Ctrl/Cmd to select multiple items."
|
||||
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_LABEL="Include Child Menu Items"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_DESC="When enabled, child menu items under the selected items will also be accessible during offline mode. For example, selecting 'legal' will also allow access to 'legal/terms-of-service' and 'legal/privacy-policy'."
|
||||
|
||||
; Help
|
||||
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
|
||||
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="<strong>Step 1:</strong> Create articles for your legal pages (Terms of Service, Privacy Policy, etc.).<br/><strong>Step 2:</strong> Create menu items pointing to those articles.<br/><strong>Step 3:</strong> Select the menu items above (hold Ctrl/Cmd to select multiple).<br/><strong>Step 4:</strong> When your site goes offline, visitors can still access the selected pages.<br/><br/><em>Tip:</em> The dropdown shows the full URL path for each menu item (e.g., /legal/terms-of-service)."
|
||||
|
||||
+4
-94
@@ -9,106 +9,16 @@
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
/**
|
||||
* MokoJoomTOS Offline Mode Bypass Plugin (Legacy)
|
||||
* MokoJoomTOS Legacy Entry Point
|
||||
*
|
||||
* Allows configured menu items to remain accessible when the site
|
||||
* is in offline mode.
|
||||
* This file is required by Joomla's plugin loader (<filename plugin="mokojoomtos">)
|
||||
* but is NOT executed under Joomla 5's DI container. The actual plugin logic lives
|
||||
* in src/Extension/MokoJoomTOS.php, bootstrapped via services/provider.php.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class PlgSystemMokojoomtos extends CMSPlugin
|
||||
{
|
||||
/**
|
||||
* Load the language file on instantiation.
|
||||
*
|
||||
* @var boolean
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
/**
|
||||
* Application object
|
||||
*
|
||||
* @var \Joomla\CMS\Application\CMSApplication
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* After route event handler
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 04.00.00
|
||||
*/
|
||||
public function onAfterRoute()
|
||||
{
|
||||
// Only process for site application
|
||||
if (!$this->app->isClient('site'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the global configuration
|
||||
$config = $this->app->getConfig();
|
||||
|
||||
// Only proceed if site is offline
|
||||
if (!$config->get('offline'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the configured slugs (stored as array for multi-select)
|
||||
$slugs = $this->params->get('tos_slug', []);
|
||||
|
||||
// Handle legacy single-value string format
|
||||
if (is_string($slugs))
|
||||
{
|
||||
$slugs = array_filter([trim($slugs)]);
|
||||
}
|
||||
|
||||
if (empty($slugs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current URI path
|
||||
$uri = Uri::getInstance();
|
||||
$path = trim($uri->getPath(), '/');
|
||||
|
||||
// Remove the base path if present
|
||||
$base = trim(Uri::base(true), '/');
|
||||
if (!empty($base) && strpos($path, $base) === 0)
|
||||
{
|
||||
$path = trim(substr($path, strlen($base)), '/');
|
||||
}
|
||||
|
||||
// Check if the path matches any configured slug
|
||||
foreach ($slugs as $slug)
|
||||
{
|
||||
$slug = trim($slug);
|
||||
if (empty($slug))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($path === $slug || strpos($path, $slug . '/') === 0)
|
||||
{
|
||||
// Temporarily disable offline mode for this request
|
||||
$config->set('offline', 0);
|
||||
|
||||
// Set component-only view (no template chrome)
|
||||
$input = $this->app->input;
|
||||
$input->set('tmpl', 'component');
|
||||
|
||||
// Also set in GET superglobal to ensure recognition
|
||||
$_GET['tmpl'] = 'component';
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -38,7 +38,7 @@
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>04.02.01</version>
|
||||
<description>PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION</description>
|
||||
<description>Allows Terms of Service to be accessible via menu slug when site is offline</description>
|
||||
|
||||
<namespace path="src">Joomla\Plugin\System\MokoJoomTOS</namespace>
|
||||
|
||||
@@ -71,6 +71,18 @@
|
||||
description="PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC"
|
||||
multiple="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="include_children"
|
||||
type="radio"
|
||||
label="PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_LABEL"
|
||||
description="PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_DESC"
|
||||
default="1"
|
||||
class="btn-group btn-group-yesno"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="help_spacer"
|
||||
|
||||
+368
-364
@@ -13,394 +13,398 @@ use Joomla\CMS\Installer\InstallerAdapter;
|
||||
use Joomla\CMS\Installer\InstallerScript;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Table\Table;
|
||||
|
||||
/**
|
||||
* Installation script for MokoJoomTOS Offline Plugin
|
||||
* Installation script for MokoJoomTOS Plugin
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class PlgSystemMokojoomtosOfflineInstallerScript extends InstallerScript
|
||||
class PlgSystemMokojoomtosInstallerScript extends InstallerScript
|
||||
{
|
||||
/**
|
||||
* Minimum Joomla version required to install the plugin
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $minimumJoomla = '4.0.0';
|
||||
/**
|
||||
* Minimum Joomla version required to install the plugin
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $minimumJoomla = '4.0.0';
|
||||
|
||||
/**
|
||||
* Minimum PHP version required to install the plugin
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $minimumPhp = '7.4.0';
|
||||
/**
|
||||
* Minimum PHP version required to install the plugin
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $minimumPhp = '7.4.0';
|
||||
|
||||
/**
|
||||
* Extension type (used by parent class)
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $extension = 'plg_system_mokojoomtos';
|
||||
/**
|
||||
* Extension type (used by parent class)
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $extension = 'plg_system_mokojoomtos';
|
||||
|
||||
/**
|
||||
* Function called before plugin installation/update/uninstall
|
||||
*
|
||||
* @param string $type Installation type (install, update, discover_install)
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function preflight($type, $parent)
|
||||
{
|
||||
// Check minimum requirements
|
||||
if (!parent::preflight($type, $parent)) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Function called before plugin installation/update/uninstall
|
||||
*
|
||||
* @param string $type Installation type (install, update, discover_install)
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return boolean True on success
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function preflight($type, $parent)
|
||||
{
|
||||
if (!parent::preflight($type, $parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called after plugin installation
|
||||
*
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function install($parent)
|
||||
{
|
||||
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_INSTALL_SUCCESS') . '</p>';
|
||||
}
|
||||
/**
|
||||
* Function called after plugin installation
|
||||
*
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function install($parent)
|
||||
{
|
||||
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_INSTALL_SUCCESS') . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called after plugin update
|
||||
*
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function update($parent)
|
||||
{
|
||||
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_UPDATE_SUCCESS') . '</p>';
|
||||
}
|
||||
/**
|
||||
* Function called after plugin update
|
||||
*
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function update($parent)
|
||||
{
|
||||
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_UPDATE_SUCCESS') . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called after plugin uninstallation
|
||||
*
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function uninstall($parent)
|
||||
{
|
||||
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_UNINSTALL_SUCCESS') . '</p>';
|
||||
}
|
||||
/**
|
||||
* Function called after plugin uninstallation
|
||||
*
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function uninstall($parent)
|
||||
{
|
||||
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_UNINSTALL_SUCCESS') . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called after extension installation/update/discover_install
|
||||
*
|
||||
* @param string $type Installation type (install, update, discover_install)
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function postflight($type, $parent)
|
||||
{
|
||||
if ($type === 'install' || $type === 'discover_install') {
|
||||
// Create Terms of Service article and menu item
|
||||
$this->createTermsOfServiceSetup();
|
||||
|
||||
echo '<div class="alert alert-success">';
|
||||
echo '<h4>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_TITLE') . '</h4>';
|
||||
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_DESC') . '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Function called after extension installation/update/discover_install
|
||||
*
|
||||
* Fixes #89: enablePlugin() is now called unconditionally for both
|
||||
* install and upgrade paths.
|
||||
* Fixes #92: enablePlugin() is called on upgrade to re-enable if disabled.
|
||||
*
|
||||
* @param string $type Installation type (install, update, discover_install)
|
||||
* @param InstallerAdapter $parent Parent installer adapter
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function postflight($type, $parent)
|
||||
{
|
||||
// Always enable the plugin on install or upgrade
|
||||
$this->enablePlugin();
|
||||
|
||||
/**
|
||||
* Create Terms of Service article and menu item
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function createTermsOfServiceSetup()
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Check if Terms of Service article already exists
|
||||
$query = $db->getQuery(true)
|
||||
->select('id')
|
||||
->from($db->quoteName('#__content'))
|
||||
->where($db->quoteName('alias') . ' = ' . $db->quote('terms-of-service'));
|
||||
$db->setQuery($query);
|
||||
$articleId = $db->loadResult();
|
||||
|
||||
// Create article if it doesn't exist
|
||||
if (!$articleId) {
|
||||
$articleId = $this->createTermsArticle();
|
||||
}
|
||||
|
||||
if ($articleId) {
|
||||
// Check if menu item already exists
|
||||
$query = $db->getQuery(true)
|
||||
->select('id')
|
||||
->from($db->quoteName('#__menu'))
|
||||
->where($db->quoteName('alias') . ' = ' . $db->quote('terms-of-service'))
|
||||
->where($db->quoteName('published') . ' >= 0');
|
||||
$db->setQuery($query);
|
||||
$menuId = $db->loadResult();
|
||||
|
||||
// Create menu item if it doesn't exist
|
||||
if (!$menuId) {
|
||||
$this->createTermsMenuItem($articleId);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::add('Error creating Terms of Service setup: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
if ($type === 'install' || $type === 'discover_install') {
|
||||
$this->createTermsOfServiceSetup();
|
||||
|
||||
/**
|
||||
* Create Terms of Service article
|
||||
*
|
||||
* @return int|null Article ID or null on failure
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function createTermsArticle()
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
echo '<div class="alert alert-success">';
|
||||
echo '<h4>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_TITLE') . '</h4>';
|
||||
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_DESC') . '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_content/tables');
|
||||
$table = Table::getInstance('Content', 'Joomla\\Component\\Content\\Administrator\\Table\\');
|
||||
/**
|
||||
* Create Terms of Service article and menu item
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function createTermsOfServiceSetup()
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
|
||||
if (!$table) {
|
||||
Log::add('Failed to get Content table instance', Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
// Check if Terms of Service article already exists (with catid filter)
|
||||
$query = $db->getQuery(true)
|
||||
->select('id')
|
||||
->from($db->quoteName('#__content'))
|
||||
->where($db->quoteName('alias') . ' = ' . $db->quote('terms-of-service'))
|
||||
->where($db->quoteName('state') . ' >= 0');
|
||||
$db->setQuery($query);
|
||||
$articleId = $db->loadResult();
|
||||
|
||||
// Get Uncategorised category ID dynamically
|
||||
$query = $db->getQuery(true)
|
||||
->select('id')
|
||||
->from($db->quoteName('#__categories'))
|
||||
->where($db->quoteName('extension') . ' = ' . $db->quote('com_content'))
|
||||
->where($db->quoteName('alias') . ' = ' . $db->quote('uncategorised'))
|
||||
->where($db->quoteName('published') . ' = 1');
|
||||
$db->setQuery($query);
|
||||
$catId = (int) $db->loadResult();
|
||||
if (!$articleId) {
|
||||
$articleId = $this->createTermsArticle();
|
||||
}
|
||||
|
||||
if (!$catId) {
|
||||
Log::add('Could not find Uncategorised category for com_content', Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
if ($articleId) {
|
||||
$query = $db->getQuery(true)
|
||||
->select('id')
|
||||
->from($db->quoteName('#__menu'))
|
||||
->where($db->quoteName('alias') . ' = ' . $db->quote('terms-of-service'))
|
||||
->where($db->quoteName('published') . ' >= 0');
|
||||
$db->setQuery($query);
|
||||
$menuId = $db->loadResult();
|
||||
|
||||
// Get current user ID for article ownership
|
||||
$createdBy = Factory::getApplication()->getIdentity()->id ?: 0;
|
||||
if (!$menuId) {
|
||||
$this->createTermsMenuItem($articleId);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::add('Error creating Terms of Service setup: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'title' => 'Terms of Service',
|
||||
'alias' => 'terms-of-service',
|
||||
'introtext' => '<h2>Terms of Service</h2><p>Welcome to our Terms of Service page.</p><p>This page will remain accessible even when the site is in offline/maintenance mode.</p>',
|
||||
'fulltext' => '',
|
||||
'state' => 1,
|
||||
'catid' => $catId,
|
||||
'created' => Factory::getDate()->toSql(),
|
||||
'created_by' => $createdBy,
|
||||
'language' => '*',
|
||||
'access' => 1, // Public
|
||||
];
|
||||
|
||||
// Bind data to table object first
|
||||
if (!$table->bind($data)) {
|
||||
Log::add('Failed to bind data to Content table: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check data validity
|
||||
if (!$table->check()) {
|
||||
Log::add('Content table check failed: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Save the table
|
||||
if (!$table->store()) {
|
||||
Log::add('Failed to store Content table: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
|
||||
echo '<p class="alert alert-info">✓ Created Terms of Service article</p>';
|
||||
return $table->id;
|
||||
} catch (\Exception $e) {
|
||||
Log::add('Error creating Terms of Service article: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Create Terms of Service article using Joomla 5 MVCFactory
|
||||
*
|
||||
* Fixes #90: Uses bootComponent()->getMVCFactory() instead of
|
||||
* the removed Table::addIncludePath() / Table::getInstance().
|
||||
* Fixes #94: Includes params, metadata, and attribs defaults.
|
||||
*
|
||||
* @return int|null Article ID or null on failure
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function createTermsArticle()
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
$app = Factory::getApplication();
|
||||
|
||||
/**
|
||||
* Create Terms of Service menu item
|
||||
*
|
||||
* @param int $articleId The article ID to link to
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function createTermsMenuItem($articleId)
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Check if "Legal" menu type exists
|
||||
$query = $db->getQuery(true)
|
||||
->select('id')
|
||||
->from($db->quoteName('#__menu_types'))
|
||||
->where($db->quoteName('menutype') . ' = ' . $db->quote('legal'));
|
||||
$db->setQuery($query);
|
||||
$legalMenuExists = $db->loadResult();
|
||||
|
||||
// Create "Legal" menu type if it doesn't exist
|
||||
if (!$legalMenuExists) {
|
||||
$this->createLegalMenuType();
|
||||
}
|
||||
|
||||
// Get com_content component ID dynamically
|
||||
$query = $db->getQuery(true)
|
||||
->select('extension_id')
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('com_content'));
|
||||
$db->setQuery($query);
|
||||
$componentId = (int) $db->loadResult();
|
||||
// Get content table via MVCFactory (Joomla 4/5 compatible)
|
||||
$table = $app->bootComponent('com_content')
|
||||
->getMVCFactory()
|
||||
->createTable('Article', 'Administrator');
|
||||
|
||||
if (!$componentId) {
|
||||
Log::add('Could not determine com_content component ID', Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_menus/tables');
|
||||
$table = Table::getInstance('Menu', 'Joomla\\Component\\Menus\\Administrator\\Table\\');
|
||||
|
||||
if (!$table) {
|
||||
Log::add('Failed to get Menu table instance', Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'menutype' => 'legal',
|
||||
'title' => 'Terms of Service',
|
||||
'alias' => 'terms-of-service',
|
||||
'link' => 'index.php?option=com_content&view=article&id=' . $articleId,
|
||||
'type' => 'component',
|
||||
'published' => 1,
|
||||
'parent_id' => 1,
|
||||
'component_id' => $componentId,
|
||||
'level' => 1,
|
||||
'language' => '*',
|
||||
'access' => 1, // Public
|
||||
'params' => '{"show_title":"1","link_titles":"0","show_intro":"","info_block_position":"","show_category":"0","link_category":"0","show_parent_category":"0","link_parent_category":"0","show_author":"0","link_author":"0","show_create_date":"0","show_modify_date":"0","show_publish_date":"0","show_item_navigation":"0","show_icons":"0","show_print_icon":"0","show_email_icon":"0","show_hits":"0","show_noauth":"0","urls_position":"","menu-anchor_title":"","menu-anchor_css":"","menu_image":"","menu_text":1,"page_title":"","show_page_heading":0,"page_heading":"","pageclass_sfx":"","menu-meta_description":"","menu-meta_keywords":"","robots":"","secure":0}',
|
||||
];
|
||||
|
||||
// Set the location in the menu tree
|
||||
$table->setLocation($data['parent_id'], 'last-child');
|
||||
|
||||
// Bind data to table object
|
||||
if (!$table->bind($data)) {
|
||||
Log::add('Failed to bind data to Menu table: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check data validity
|
||||
if (!$table->check()) {
|
||||
Log::add('Menu table check failed: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the menu item
|
||||
if (!$table->store()) {
|
||||
Log::add('Failed to store Menu table: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<p class="alert alert-info">✓ Created Terms of Service menu item in Legal menu with slug: terms-of-service</p>';
|
||||
|
||||
// Enable the plugin
|
||||
$this->enablePlugin();
|
||||
} catch (\Exception $e) {
|
||||
Log::add('Error creating Terms of Service menu item: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
if (!$table) {
|
||||
Log::add('Failed to get Content table instance', Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Legal menu type
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function createLegalMenuType()
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Insert the Legal menu type
|
||||
$query = $db->getQuery(true)
|
||||
->insert($db->quoteName('#__menu_types'))
|
||||
->columns($db->quoteName(['menutype', 'title', 'description']))
|
||||
->values(
|
||||
$db->quote('legal') . ', ' .
|
||||
$db->quote('Legal') . ', ' .
|
||||
$db->quote('Legal documents and policies menu')
|
||||
);
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
|
||||
echo '<p class="alert alert-info">✓ Created Legal menu type</p>';
|
||||
} catch (\Exception $e) {
|
||||
Log::add('Error creating Legal menu type: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
// Get Uncategorised category ID dynamically
|
||||
$query = $db->getQuery(true)
|
||||
->select('id')
|
||||
->from($db->quoteName('#__categories'))
|
||||
->where($db->quoteName('extension') . ' = ' . $db->quote('com_content'))
|
||||
->where($db->quoteName('alias') . ' = ' . $db->quote('uncategorised'))
|
||||
->where($db->quoteName('published') . ' = 1');
|
||||
$db->setQuery($query);
|
||||
$catId = (int) $db->loadResult();
|
||||
|
||||
/**
|
||||
* Enable the plugin after installation
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function enablePlugin()
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__extensions'))
|
||||
->set($db->quoteName('enabled') . ' = 1')
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomtos'));
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
|
||||
echo '<p class="alert alert-success">✓ Plugin enabled automatically</p>';
|
||||
} catch (\Exception $e) {
|
||||
Log::add('Error enabling plugin: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
if (!$catId) {
|
||||
Log::add('Could not find Uncategorised category for com_content', Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
|
||||
$createdBy = $app->getIdentity()->id ?: 0;
|
||||
|
||||
$data = [
|
||||
'title' => 'Terms of Service',
|
||||
'alias' => 'terms-of-service',
|
||||
'introtext' => '<h2>Terms of Service</h2><p>Welcome to our Terms of Service page.</p><p>This page will remain accessible even when the site is in offline/maintenance mode.</p>',
|
||||
'fulltext' => '',
|
||||
'state' => 1,
|
||||
'catid' => $catId,
|
||||
'created' => Factory::getDate()->toSql(),
|
||||
'created_by' => $createdBy,
|
||||
'language' => '*',
|
||||
'access' => 1,
|
||||
'params' => '{}',
|
||||
'metadata' => '{"robots":"","author":"","rights":""}',
|
||||
'attribs' => '{}',
|
||||
];
|
||||
|
||||
if (!$table->bind($data)) {
|
||||
Log::add('Failed to bind data to Content table: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$table->check()) {
|
||||
Log::add('Content table check failed: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$table->store()) {
|
||||
Log::add('Failed to store Content table: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return null;
|
||||
}
|
||||
|
||||
echo '<p class="alert alert-info">Created Terms of Service article</p>';
|
||||
return $table->id;
|
||||
} catch (\Exception $e) {
|
||||
Log::add('Error creating Terms of Service article: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Terms of Service menu item using Joomla 5 MVCFactory
|
||||
*
|
||||
* Fixes #90: Uses bootComponent()->getMVCFactory() instead of
|
||||
* the removed Table::addIncludePath() / Table::getInstance().
|
||||
*
|
||||
* @param int $articleId The article ID to link to
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function createTermsMenuItem($articleId)
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
$app = Factory::getApplication();
|
||||
|
||||
// Check if "Legal" menu type exists
|
||||
$query = $db->getQuery(true)
|
||||
->select('id')
|
||||
->from($db->quoteName('#__menu_types'))
|
||||
->where($db->quoteName('menutype') . ' = ' . $db->quote('legal'));
|
||||
$db->setQuery($query);
|
||||
$legalMenuExists = $db->loadResult();
|
||||
|
||||
if (!$legalMenuExists) {
|
||||
$this->createLegalMenuType();
|
||||
}
|
||||
|
||||
// Get com_content component ID dynamically
|
||||
$query = $db->getQuery(true)
|
||||
->select('extension_id')
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('com_content'));
|
||||
$db->setQuery($query);
|
||||
$componentId = (int) $db->loadResult();
|
||||
|
||||
if (!$componentId) {
|
||||
Log::add('Could not determine com_content component ID', Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get menu table via MVCFactory (Joomla 4/5 compatible)
|
||||
$table = $app->bootComponent('com_menus')
|
||||
->getMVCFactory()
|
||||
->createTable('Menu', 'Administrator');
|
||||
|
||||
if (!$table) {
|
||||
Log::add('Failed to get Menu table instance', Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'menutype' => 'legal',
|
||||
'title' => 'Terms of Service',
|
||||
'alias' => 'terms-of-service',
|
||||
'link' => 'index.php?option=com_content&view=article&id=' . $articleId,
|
||||
'type' => 'component',
|
||||
'published' => 1,
|
||||
'parent_id' => 1,
|
||||
'component_id' => $componentId,
|
||||
'level' => 1,
|
||||
'language' => '*',
|
||||
'access' => 1,
|
||||
'params' => '{"show_title":"1","link_titles":"0","show_intro":"","info_block_position":"","show_category":"0","link_category":"0","show_parent_category":"0","link_parent_category":"0","show_author":"0","link_author":"0","show_create_date":"0","show_modify_date":"0","show_publish_date":"0","show_item_navigation":"0","show_icons":"0","show_print_icon":"0","show_email_icon":"0","show_hits":"0","show_noauth":"0","urls_position":"","menu-anchor_title":"","menu-anchor_css":"","menu_image":"","menu_text":1,"page_title":"","show_page_heading":0,"page_heading":"","pageclass_sfx":"","menu-meta_description":"","menu-meta_keywords":"","robots":"","secure":0}',
|
||||
];
|
||||
|
||||
$table->setLocation($data['parent_id'], 'last-child');
|
||||
|
||||
if (!$table->bind($data)) {
|
||||
Log::add('Failed to bind data to Menu table: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$table->check()) {
|
||||
Log::add('Menu table check failed: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$table->store()) {
|
||||
Log::add('Failed to store Menu table: ' . $table->getError(), Log::WARNING, 'jerror');
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<p class="alert alert-info">Created Terms of Service menu item in Legal menu</p>';
|
||||
} catch (\Exception $e) {
|
||||
Log::add('Error creating Terms of Service menu item: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Legal menu type
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function createLegalMenuType()
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->insert($db->quoteName('#__menu_types'))
|
||||
->columns($db->quoteName(['menutype', 'title', 'description']))
|
||||
->values(
|
||||
$db->quote('legal') . ', ' .
|
||||
$db->quote('Legal') . ', ' .
|
||||
$db->quote('Legal documents and policies menu')
|
||||
);
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
|
||||
echo '<p class="alert alert-info">Created Legal menu type</p>';
|
||||
} catch (\Exception $e) {
|
||||
// Duplicate key is expected if race condition — safe to ignore
|
||||
Log::add('Error creating Legal menu type: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the plugin after installation
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function enablePlugin()
|
||||
{
|
||||
try {
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__extensions'))
|
||||
->set($db->quoteName('enabled') . ' = 1')
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomtos'));
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
} catch (\Exception $e) {
|
||||
Log::add('Error enabling plugin: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Joomla\Plugin\System\MokoJoomTOS\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
@@ -53,23 +54,21 @@ final class MokoJoomTOS extends CMSPlugin implements SubscriberInterface
|
||||
* the site is in offline mode. If both conditions are met, temporarily
|
||||
* disables offline mode and sets component-only view for this request.
|
||||
*
|
||||
* This event fires after routing but before template selection, making it
|
||||
* the correct place to set tmpl=component to prevent template chrome loading.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function onAfterRoute()
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
// Only process for site application
|
||||
if (!$this->getApplication()->isClient('site'))
|
||||
if (!$app->isClient('site'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the global configuration
|
||||
$config = $this->getApplication()->getConfig();
|
||||
$config = $app->getConfig();
|
||||
|
||||
// Only proceed if site is offline
|
||||
if (!$config->get('offline'))
|
||||
@@ -77,7 +76,7 @@ final class MokoJoomTOS extends CMSPlugin implements SubscriberInterface
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the configured slugs (stored as array for multi-select)
|
||||
// Get the configured slugs — cast to array to handle stdClass from Registry (#96)
|
||||
$slugs = $this->params->get('tos_slug', []);
|
||||
|
||||
// Handle legacy single-value string format
|
||||
@@ -85,46 +84,161 @@ final class MokoJoomTOS extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
$slugs = array_filter([trim($slugs)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$slugs = (array) $slugs;
|
||||
}
|
||||
|
||||
if (empty($slugs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current URI path
|
||||
$uri = Uri::getInstance();
|
||||
$path = trim($uri->getPath(), '/');
|
||||
$includeChildren = (int) $this->params->get('include_children', 1);
|
||||
|
||||
// Remove the base path if present
|
||||
// Try SEF path matching first, then fall back to Itemid matching (#91)
|
||||
if ($this->matchByPath($slugs, $config, $app, $includeChildren))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->matchByItemId($slugs, $config, $app, $includeChildren);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the current request path against configured slugs (SEF mode)
|
||||
*
|
||||
* @param array $slugs Configured slug values
|
||||
* @param object $config Joomla configuration object
|
||||
* @param object $app Application instance
|
||||
* @param integer $includeChildren Whether to include child menu items
|
||||
*
|
||||
* @return boolean True if a match was found and offline mode was bypassed
|
||||
*
|
||||
* @since 4.1.0
|
||||
*/
|
||||
private function matchByPath(array $slugs, $config, $app, int $includeChildren = 1): bool
|
||||
{
|
||||
$uri = Uri::getInstance();
|
||||
$path = urldecode(trim($uri->getPath(), '/'));
|
||||
|
||||
// Remove the base path if present (subdirectory installs)
|
||||
$base = trim(Uri::base(true), '/');
|
||||
if (!empty($base) && strpos($path, $base) === 0)
|
||||
{
|
||||
$path = trim(substr($path, strlen($base)), '/');
|
||||
}
|
||||
|
||||
// Check if the path matches any configured slug
|
||||
// Skip if path is empty or just index.php (non-SEF)
|
||||
if (empty($path) || $path === 'index.php')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($slugs as $slug)
|
||||
{
|
||||
$slug = trim($slug);
|
||||
$slug = trim((string) $slug);
|
||||
if (empty($slug))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($path === $slug || strpos($path, $slug . '/') === 0)
|
||||
$isMatch = ($path === $slug)
|
||||
|| ($includeChildren && strpos($path, $slug . '/') === 0);
|
||||
|
||||
if ($isMatch)
|
||||
{
|
||||
// Temporarily disable offline mode for this request
|
||||
$config->set('offline', 0);
|
||||
|
||||
// Set component-only view (no template chrome)
|
||||
$input = $this->getApplication()->input;
|
||||
$input->set('tmpl', 'component');
|
||||
|
||||
// Also set in GET superglobal to ensure recognition
|
||||
$_GET['tmpl'] = 'component';
|
||||
|
||||
return;
|
||||
$this->bypassOffline($config, $app);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the current request Itemid against menu items for configured slugs (non-SEF fallback)
|
||||
*
|
||||
* When SEF URLs are disabled, the path is just index.php so we match by
|
||||
* checking if the requested Itemid belongs to a menu item whose path
|
||||
* matches a configured slug.
|
||||
*
|
||||
* @param array $slugs Configured slug values
|
||||
* @param object $config Joomla configuration object
|
||||
* @param object $app Application instance
|
||||
* @param integer $includeChildren Whether to include child menu items
|
||||
*
|
||||
* @return boolean True if a match was found and offline mode was bypassed
|
||||
*
|
||||
* @since 4.1.0
|
||||
*/
|
||||
private function matchByItemId(array $slugs, $config, $app, int $includeChildren = 1): bool
|
||||
{
|
||||
$itemId = (int) $app->input->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 = $db->loadResult();
|
||||
|
||||
if (!$menuPath)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$menuPath = trim($menuPath, '/');
|
||||
|
||||
foreach ($slugs as $slug)
|
||||
{
|
||||
$slug = trim((string) $slug);
|
||||
if (empty($slug))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$isMatch = ($menuPath === $slug)
|
||||
|| ($includeChildren && strpos($menuPath, $slug . '/') === 0);
|
||||
|
||||
if ($isMatch)
|
||||
{
|
||||
$this->bypassOffline($config, $app);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
// Silently fail — do not bypass offline mode on error
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bypass offline mode and set component-only view for this request
|
||||
*
|
||||
* @param object $config Joomla configuration object
|
||||
* @param object $app Application instance
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.1.0
|
||||
*/
|
||||
private function bypassOffline($config, $app): void
|
||||
{
|
||||
$config->set('offline', 0);
|
||||
$app->input->set('tmpl', 'component');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,24 @@ class MenuslugField extends ListField
|
||||
{
|
||||
$options = parent::getOptions();
|
||||
|
||||
// Warn if SEF URLs are disabled (#97)
|
||||
try
|
||||
{
|
||||
$sef = Factory::getApplication()->get('sef', true);
|
||||
if (!$sef)
|
||||
{
|
||||
$options[] = (object) [
|
||||
'value' => '',
|
||||
'text' => Text::_('PLG_SYSTEM_MOKOJOOMTOS_FIELD_SEF_WARNING'),
|
||||
'disabled' => true
|
||||
];
|
||||
}
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
// Ignore — field still works without the warning
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
@@ -70,7 +88,7 @@ class MenuslugField extends ListField
|
||||
$options[] = (object) [
|
||||
'value' => '',
|
||||
'text' => '──────────────',
|
||||
'disable' => true
|
||||
'disabled' => true
|
||||
];
|
||||
}
|
||||
$lastMenuType = $item->menutype;
|
||||
|
||||
Reference in New Issue
Block a user