Files
MokoSuiteClient/source/packages/com_mokosuite/admin/src/Model/ImportModel.php
T
Jonathan Miller 00d44256b4 refactor: rename MokoWaaS to MokoSuite across entire codebase
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.
2026-06-07 09:25:45 -05:00

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');
}
}
}