00d44256b4
Rebrand all 17 sub-extensions from mokowaas to mokosuite naming, including component, plugins, modules, task plugins, and webservices. Updates package manifest, workflows, docs, wiki, and issue templates. Adds new plg_system_mokosuite_license extension.
689 lines
17 KiB
PHP
689 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* @package MokoSuite
|
|
* @subpackage com_mokosuite
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
*/
|
|
|
|
namespace Moko\Component\MokoSuite\Administrator\Model;
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\Log\Log;
|
|
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
|
use Joomla\Registry\Registry;
|
|
|
|
/**
|
|
* Importer for migrating from Akeeba Admin Tools to MokoSuite.
|
|
*
|
|
* Reads Admin Tools WAF config, htaccess settings, IP blocklists,
|
|
* and security headers — maps them to MokoSuite firewall plugin params
|
|
* and htaccess maker options.
|
|
*
|
|
* @since 02.32.00
|
|
*/
|
|
class ImportModel extends BaseDatabaseModel
|
|
{
|
|
/**
|
|
* Check if Admin Tools data is available for import.
|
|
* Returns null if already imported or no data found.
|
|
*/
|
|
public function checkAdminToolsAvailable(): ?object
|
|
{
|
|
if ($this->wasImported('admintools'))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$db = $this->getDatabase();
|
|
|
|
try
|
|
{
|
|
$result = (object) [
|
|
'component' => false,
|
|
'waf_config' => false,
|
|
'storage' => false,
|
|
'ip_blocks' => 0,
|
|
];
|
|
|
|
// Check component
|
|
$db->setQuery("SELECT COUNT(*) FROM #__extensions WHERE element = 'com_admintools' AND type = 'component'");
|
|
$result->component = (int) $db->loadResult() > 0;
|
|
|
|
// Check WAF config table
|
|
$db->setQuery('SHOW TABLES LIKE ' . $db->quote('%admintools_wafconfig%'));
|
|
|
|
if ($db->loadResult())
|
|
{
|
|
$result->waf_config = true;
|
|
$db->setQuery('SELECT COUNT(*) FROM #__admintools_wafconfig');
|
|
$result->waf_settings = (int) $db->loadResult();
|
|
}
|
|
|
|
// Check storage table
|
|
$db->setQuery('SHOW TABLES LIKE ' . $db->quote('%admintools_storage%'));
|
|
|
|
if ($db->loadResult())
|
|
{
|
|
$result->storage = true;
|
|
}
|
|
|
|
// Check IP blocklist
|
|
$db->setQuery('SHOW TABLES LIKE ' . $db->quote('%admintools_ipblock%'));
|
|
|
|
if ($db->loadResult())
|
|
{
|
|
$db->setQuery('SELECT COUNT(*) FROM #__admintools_ipblock');
|
|
$result->ip_blocks = (int) $db->loadResult();
|
|
}
|
|
|
|
// Only available if at least one data source exists
|
|
if (!$result->component && !$result->waf_config && !$result->storage)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import Admin Tools settings into MokoSuite.
|
|
*/
|
|
public function importAdminTools(): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$results = ['firewall' => 0, 'htaccess' => 0, 'ip_blocks' => 0, 'disabled' => false];
|
|
|
|
try
|
|
{
|
|
// ============================================================
|
|
// 1. Import WAF Config → Firewall plugin params
|
|
// ============================================================
|
|
$wafSettings = $this->readWafConfig($db);
|
|
$firewallParams = $this->mapWafToFirewall($wafSettings);
|
|
|
|
if (!empty($firewallParams))
|
|
{
|
|
$this->mergePluginParams('mokosuite_firewall', 'system', $firewallParams);
|
|
$results['firewall'] = \count($firewallParams);
|
|
}
|
|
|
|
// ============================================================
|
|
// 2. Import htaccess settings → component htaccess options
|
|
// ============================================================
|
|
$htaccessSettings = $this->readHtaccessConfig($db);
|
|
$htaccessOptions = $this->mapToHtaccess($htaccessSettings, $wafSettings);
|
|
|
|
if (!empty($htaccessOptions))
|
|
{
|
|
$this->mergeComponentHtaccessOptions($htaccessOptions);
|
|
$results['htaccess'] = \count($htaccessOptions);
|
|
}
|
|
|
|
// ============================================================
|
|
// 3. Import IP blocklist → Firewall IP deny list
|
|
// ============================================================
|
|
$ipBlocks = $this->readIpBlocklist($db);
|
|
|
|
if (!empty($ipBlocks))
|
|
{
|
|
$this->mergeIpBlocklist($ipBlocks);
|
|
$results['ip_blocks'] = \count($ipBlocks);
|
|
}
|
|
|
|
// ============================================================
|
|
// 4. Disable Admin Tools
|
|
// ============================================================
|
|
$this->disableAdminTools($db);
|
|
$results['disabled'] = true;
|
|
|
|
$this->markImported('admintools');
|
|
|
|
return [
|
|
'success' => true,
|
|
'message' => \sprintf(
|
|
'Imported %d firewall settings, %d htaccess options, %d blocked IPs from Admin Tools. Admin Tools has been disabled.',
|
|
$results['firewall'], $results['htaccess'], $results['ip_blocks']
|
|
),
|
|
'counts' => $results,
|
|
];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return ['success' => false, 'message' => 'Import failed: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read WAF config from #__admintools_wafconfig.
|
|
*/
|
|
private function readWafConfig($db): array
|
|
{
|
|
try
|
|
{
|
|
$db->setQuery('SHOW TABLES LIKE ' . $db->quote('%admintools_wafconfig%'));
|
|
|
|
if (!$db->loadResult())
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$db->setQuery('SELECT * FROM #__admintools_wafconfig');
|
|
$rows = $db->loadObjectList() ?: [];
|
|
|
|
$config = [];
|
|
|
|
foreach ($rows as $row)
|
|
{
|
|
$key = $row->key ?? $row->option ?? '';
|
|
|
|
if (!empty($key))
|
|
{
|
|
$config[$key] = $row->value ?? '';
|
|
}
|
|
}
|
|
|
|
return $config;
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read htaccess/server config from #__admintools_storage.
|
|
*/
|
|
private function readHtaccessConfig($db): array
|
|
{
|
|
try
|
|
{
|
|
$db->setQuery('SHOW TABLES LIKE ' . $db->quote('%admintools_storage%'));
|
|
|
|
if (!$db->loadResult())
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$db->setQuery('SELECT * FROM #__admintools_storage');
|
|
$rows = $db->loadObjectList() ?: [];
|
|
|
|
$config = [];
|
|
|
|
foreach ($rows as $row)
|
|
{
|
|
$key = $row->key ?? '';
|
|
|
|
if (!empty($key))
|
|
{
|
|
$config[$key] = $row->value ?? '';
|
|
}
|
|
}
|
|
|
|
return $config;
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read IP blocklist from #__admintools_ipblock.
|
|
*/
|
|
private function readIpBlocklist($db): array
|
|
{
|
|
try
|
|
{
|
|
$db->setQuery('SHOW TABLES LIKE ' . $db->quote('%admintools_ipblock%'));
|
|
|
|
if (!$db->loadResult())
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$db->setQuery('SELECT ip FROM #__admintools_ipblock');
|
|
|
|
return $db->loadColumn() ?: [];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map Admin Tools WAF config to MokoSuite firewall plugin params.
|
|
*/
|
|
private function mapWafToFirewall(array $waf): array
|
|
{
|
|
$params = [];
|
|
|
|
// WAF shields
|
|
if (isset($waf['sqlishield']))
|
|
{
|
|
$params['waf_sqli'] = (int) $waf['sqlishield'] ? 1 : 0;
|
|
}
|
|
|
|
if (isset($waf['antispam']))
|
|
{
|
|
$params['waf_xss'] = (int) $waf['antispam'] ? 1 : 0;
|
|
}
|
|
|
|
if (isset($waf['muashield']))
|
|
{
|
|
$params['waf_mua'] = (int) $waf['muashield'] ? 1 : 0;
|
|
}
|
|
|
|
if (isset($waf['rfishield']))
|
|
{
|
|
$params['waf_rfi'] = (int) $waf['rfishield'] ? 1 : 0;
|
|
}
|
|
|
|
if (isset($waf['dfishield']))
|
|
{
|
|
$params['waf_dfi'] = (int) $waf['dfishield'] ? 1 : 0;
|
|
}
|
|
|
|
if (isset($waf['uploadshield']))
|
|
{
|
|
// Map to our block_direct_php
|
|
$params['block_direct_php'] = (int) $waf['uploadshield'] ? 1 : 0;
|
|
}
|
|
|
|
// Admin secret URL
|
|
if (!empty($waf['adminpw']))
|
|
{
|
|
$params['admin_secret'] = $waf['adminpw'];
|
|
}
|
|
|
|
// Block frontend super user login
|
|
if (isset($waf['nofesalogin']))
|
|
{
|
|
$params['block_frontend_superuser'] = (int) $waf['nofesalogin'] ? 1 : 0;
|
|
}
|
|
|
|
// Session timeout
|
|
if (!empty($waf['sessionshield']) && !empty($waf['session_timeout']))
|
|
{
|
|
$params['admin_session_timeout'] = (int) $waf['session_timeout'];
|
|
}
|
|
|
|
// Template switch blocking
|
|
if (isset($waf['tmpl']))
|
|
{
|
|
$params['block_template_switch'] = (int) $waf['tmpl'] ? 1 : 0;
|
|
}
|
|
|
|
// Blocked sensitive files
|
|
if (isset($waf['hogfiles']))
|
|
{
|
|
$params['block_sensitive_files'] = (int) $waf['hogfiles'] ? 1 : 0;
|
|
}
|
|
|
|
return $params;
|
|
}
|
|
|
|
/**
|
|
* Map Admin Tools config to MokoSuite htaccess maker options.
|
|
*/
|
|
private function mapToHtaccess(array $storage, array $waf): array
|
|
{
|
|
$opts = [];
|
|
|
|
// Server signature
|
|
if (isset($waf['serversignature']) || isset($storage['serversignature']))
|
|
{
|
|
$opts['disable_server_signature'] = 1;
|
|
}
|
|
|
|
// Clickjacking
|
|
if (isset($waf['clickjacking']) || isset($storage['xframeoptions']))
|
|
{
|
|
$opts['prevent_clickjacking'] = 1;
|
|
}
|
|
|
|
// HSTS
|
|
if (!empty($storage['hstsheader']) || !empty($waf['hstsheader']))
|
|
{
|
|
$opts['hsts_enabled'] = 1;
|
|
|
|
if (!empty($storage['hstsmaxage']))
|
|
{
|
|
$opts['hsts_max_age'] = (int) $storage['hstsmaxage'];
|
|
}
|
|
}
|
|
|
|
// GZip
|
|
if (isset($storage['gzipcompression']))
|
|
{
|
|
$opts['enable_gzip'] = (int) $storage['gzipcompression'] ? 1 : 0;
|
|
}
|
|
|
|
// Expiration
|
|
if (isset($storage['exptime']))
|
|
{
|
|
$opts['enable_expires'] = (int) $storage['exptime'] ? 1 : 0;
|
|
}
|
|
|
|
// ETag
|
|
if (isset($storage['etagtype']))
|
|
{
|
|
$opts['etag_control'] = ($storage['etagtype'] === 'none') ? 1 : 0;
|
|
}
|
|
|
|
// Redirect www / non-www
|
|
if (!empty($storage['wwwredir']))
|
|
{
|
|
$map = ['www' => 'www', 'nowww' => 'non-www'];
|
|
$opts['www_redirect'] = $map[$storage['wwwredir']] ?? 'off';
|
|
}
|
|
|
|
// Directory listing
|
|
if (isset($storage['nodirlisting']))
|
|
{
|
|
$opts['disable_directory_listing'] = (int) $storage['nodirlisting'] ? 1 : 0;
|
|
}
|
|
|
|
// Block PHP in uploads
|
|
if (isset($storage['phpuploadexec']))
|
|
{
|
|
$opts['block_php_in_uploads'] = (int) $storage['phpuploadexec'] ? 1 : 0;
|
|
}
|
|
|
|
// Sensitive files
|
|
if (isset($storage['hogfiles']))
|
|
{
|
|
$opts['block_sensitive_files'] = (int) $storage['hogfiles'] ? 1 : 0;
|
|
}
|
|
|
|
return $opts;
|
|
}
|
|
|
|
/**
|
|
* Merge params into a plugin's existing params.
|
|
*/
|
|
private function mergePluginParams(string $element, string $folder, array $newParams): void
|
|
{
|
|
$db = $this->getDatabase();
|
|
|
|
$query = $db->getQuery(true)
|
|
->select($db->quoteName('params'))
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote($element))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
|
->where($db->quoteName('folder') . ' = ' . $db->quote($folder));
|
|
$db->setQuery($query);
|
|
$current = new Registry($db->loadResult() ?? '{}');
|
|
|
|
foreach ($newParams as $key => $value)
|
|
{
|
|
$current->set($key, $value);
|
|
}
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('params') . ' = ' . $db->quote($current->toString()))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote($element))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
|
->where($db->quoteName('folder') . ' = ' . $db->quote($folder))
|
|
)->execute();
|
|
}
|
|
|
|
/**
|
|
* Merge htaccess options into the component params.
|
|
*/
|
|
private function mergeComponentHtaccessOptions(array $options): void
|
|
{
|
|
$db = $this->getDatabase();
|
|
|
|
$query = $db->getQuery(true)
|
|
->select($db->quoteName('params'))
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('component'));
|
|
$db->setQuery($query);
|
|
$params = new Registry($db->loadResult() ?? '{}');
|
|
|
|
$htaccess = (array) json_decode(json_encode($params->get('htaccess', new \stdClass())), true);
|
|
|
|
foreach ($options as $key => $value)
|
|
{
|
|
$htaccess[$key] = $value;
|
|
}
|
|
|
|
$params->set('htaccess', $htaccess);
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
|
|
)->execute();
|
|
}
|
|
|
|
/**
|
|
* Merge imported IPs into the firewall IP blocklist.
|
|
*/
|
|
private function mergeIpBlocklist(array $ips): void
|
|
{
|
|
$db = $this->getDatabase();
|
|
|
|
$query = $db->getQuery(true)
|
|
->select($db->quoteName('params'))
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuite_firewall'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
|
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
|
|
$db->setQuery($query);
|
|
$params = new Registry($db->loadResult() ?? '{}');
|
|
|
|
$blocklist = json_decode($params->get('ip_blocklist', '[]'), true) ?: [];
|
|
|
|
$existingIps = array_column($blocklist, 'ip');
|
|
|
|
foreach ($ips as $ip)
|
|
{
|
|
$ip = trim($ip);
|
|
|
|
if (empty($ip) || \in_array($ip, $existingIps, true))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$blocklist[] = [
|
|
'ip' => $ip,
|
|
'enabled' => '1',
|
|
'label' => 'Imported from Admin Tools',
|
|
];
|
|
}
|
|
|
|
$params->set('ip_blocklist', json_encode($blocklist));
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuite_firewall'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
|
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
|
)->execute();
|
|
}
|
|
|
|
/**
|
|
* Disable Admin Tools component and plugins.
|
|
*/
|
|
private function disableAdminTools($db): void
|
|
{
|
|
// Disable component
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('enabled') . ' = 0')
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('com_admintools'))
|
|
)->execute();
|
|
|
|
// Disable all Admin Tools plugins
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('enabled') . ' = 0')
|
|
->where($db->quoteName('element') . ' LIKE ' . $db->quote('admintools%'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
|
)->execute();
|
|
|
|
Log::add('Admin Tools component and plugins disabled after MokoSuite import', Log::INFO, 'mokosuite');
|
|
}
|
|
|
|
// ==================================================================
|
|
// Akeeba Ticket System Import
|
|
// ==================================================================
|
|
|
|
/**
|
|
* Check if ATS tables exist.
|
|
* Returns null if already imported or no data found.
|
|
*/
|
|
public function checkAtsAvailable(): ?object
|
|
{
|
|
if ($this->wasImported('ats'))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$db = $this->getDatabase();
|
|
|
|
try
|
|
{
|
|
$db->setQuery('SHOW TABLES LIKE ' . $db->quote('%ats_tickets%'));
|
|
|
|
if (!$db->loadResult())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$db->setQuery('SELECT COUNT(*) FROM #__ats_tickets');
|
|
$tickets = (int) $db->loadResult();
|
|
|
|
$db->setQuery('SELECT COUNT(*) FROM #__ats_posts');
|
|
$posts = (int) $db->loadResult();
|
|
|
|
return (object) ['tickets' => $tickets, 'posts' => $posts];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import from Akeeba Ticket System and disable it.
|
|
*/
|
|
public function importAts(): array
|
|
{
|
|
// Delegate to TicketsModel for the actual import
|
|
$ticketsModel = new TicketsModel();
|
|
$result = $ticketsModel->importFromAts();
|
|
|
|
if (!$result['success'])
|
|
{
|
|
return $result;
|
|
}
|
|
|
|
// Disable ATS after successful import
|
|
try
|
|
{
|
|
$db = $this->getDatabase();
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('enabled') . ' = 0')
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('com_ats'))
|
|
)->execute();
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('enabled') . ' = 0')
|
|
->where($db->quoteName('element') . ' LIKE ' . $db->quote('ats%'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
|
)->execute();
|
|
|
|
$result['message'] .= ' Akeeba Ticket System has been disabled.';
|
|
Log::add('Akeeba Ticket System disabled after MokoSuite import', Log::INFO, 'mokosuite');
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
$result['message'] .= ' Warning: could not disable ATS: ' . $e->getMessage();
|
|
}
|
|
|
|
$this->markImported('ats');
|
|
|
|
return $result;
|
|
}
|
|
|
|
// ==================================================================
|
|
// Import markers (stored in component params)
|
|
// ==================================================================
|
|
|
|
private function wasImported(string $key): bool
|
|
{
|
|
try
|
|
{
|
|
$db = $this->getDatabase();
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select($db->quoteName('params'))
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
|
|
);
|
|
$params = new Registry($db->loadResult() ?? '{}');
|
|
|
|
return (bool) $params->get('imported_' . $key, false);
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function markImported(string $key): void
|
|
{
|
|
try
|
|
{
|
|
$db = $this->getDatabase();
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select($db->quoteName('params'))
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
|
|
);
|
|
$params = new Registry($db->loadResult() ?? '{}');
|
|
$params->set('imported_' . $key, 1);
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuite'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
|
|
)->execute();
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
Log::add('Import marker error: ' . $e->getMessage(), Log::WARNING, 'mokosuite');
|
|
}
|
|
}
|
|
}
|