chore: cascade main → dev (e7de6e4) [skip ci]
#22
@@ -15,7 +15,7 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.01.36
|
||||
VERSION: 02.01.35
|
||||
PATH: /README.md
|
||||
BRIEF: Rebranding plugin for MokoWaaS platform
|
||||
NOTE: Internal WaaS identity abstraction layer
|
||||
|
||||
+8
-897
@@ -1090,89 +1090,25 @@ class MokoWaaS extends CMSPlugin
|
||||
// Collect diagnostics
|
||||
$checks = $this->collectHealthChecks();
|
||||
|
||||
// Determine overall status and collect reasons
|
||||
// Determine overall status from individual checks
|
||||
$overall = 'ok';
|
||||
$reasons = [];
|
||||
|
||||
foreach ($checks as $name => $check)
|
||||
foreach ($checks as $check)
|
||||
{
|
||||
$checkStatus = $check['status'] ?? 'ok';
|
||||
|
||||
if ($checkStatus === 'error')
|
||||
if (($check['status'] ?? 'ok') === 'error')
|
||||
{
|
||||
$overall = 'error';
|
||||
$reasons[] = $name . ': ' . ($check['message'] ?? 'error');
|
||||
break;
|
||||
}
|
||||
elseif ($checkStatus === 'degraded')
|
||||
{
|
||||
if ($overall !== 'error')
|
||||
{
|
||||
$overall = 'degraded';
|
||||
}
|
||||
|
||||
// Build human-readable reason
|
||||
if ($name === 'extensions'
|
||||
&& isset($check['pending_updates']))
|
||||
{
|
||||
$reasons[] = $check['pending_updates']
|
||||
. ' extension update'
|
||||
. ($check['pending_updates'] > 1 ? 's' : '')
|
||||
. ' available';
|
||||
}
|
||||
elseif ($name === 'filesystem'
|
||||
&& isset($check['free_disk_mb'])
|
||||
&& $check['free_disk_mb'] < 100)
|
||||
{
|
||||
$reasons[] = 'Low disk space: '
|
||||
. $check['free_disk_mb'] . ' MB free';
|
||||
}
|
||||
elseif ($name === 'backup')
|
||||
{
|
||||
if (!empty($check['message']))
|
||||
{
|
||||
$reasons[] = $check['message'];
|
||||
}
|
||||
elseif (isset($check['days_since'])
|
||||
&& $check['days_since'] > 7)
|
||||
{
|
||||
$reasons[] = 'Last backup '
|
||||
. $check['days_since'] . ' days ago';
|
||||
}
|
||||
elseif (isset($check['last_status'])
|
||||
&& $check['last_status'] !== 'complete')
|
||||
{
|
||||
$reasons[] = 'Last backup status: '
|
||||
. $check['last_status'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$reasons[] = 'Backup: degraded';
|
||||
}
|
||||
}
|
||||
elseif ($name === 'ssl' && isset($check['days_left']))
|
||||
{
|
||||
$reasons[] = 'SSL expires in '
|
||||
. $check['days_left'] . ' days';
|
||||
}
|
||||
elseif ($name === 'cron' && isset($check['failed_24h']))
|
||||
{
|
||||
$reasons[] = $check['failed_24h']
|
||||
. ' scheduled task(s) failed';
|
||||
}
|
||||
elseif ($name === 'config' && !empty($check['issues']))
|
||||
{
|
||||
$reasons[] = implode(', ', $check['issues']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$reasons[] = $name . ': degraded';
|
||||
}
|
||||
if (($check['status'] ?? 'ok') === 'degraded')
|
||||
{
|
||||
$overall = 'degraded';
|
||||
}
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'status' => $overall,
|
||||
'reason' => implode('; ', $reasons) ?: null,
|
||||
'timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
|
||||
'checks' => $checks,
|
||||
'meta' => $this->collectHealthMeta(),
|
||||
@@ -1193,26 +1129,12 @@ class MokoWaaS extends CMSPlugin
|
||||
*/
|
||||
protected function collectHealthChecks()
|
||||
{
|
||||
$checks = [
|
||||
return [
|
||||
'database' => $this->checkDatabase(),
|
||||
'filesystem' => $this->checkFilesystem(),
|
||||
'cache' => $this->checkCache(),
|
||||
'extensions' => $this->checkExtensions(),
|
||||
'backup' => $this->checkAkeebaBackup(),
|
||||
'security' => $this->checkAdminTools(),
|
||||
'ssl' => $this->checkSsl(),
|
||||
'cron' => $this->checkScheduledTasks(),
|
||||
'errors' => $this->checkErrorLog(),
|
||||
'db_size' => $this->checkDatabaseSize(),
|
||||
'content' => $this->checkContent(),
|
||||
'users' => $this->checkUserActivity(),
|
||||
'mail' => $this->checkMail(),
|
||||
'seo' => $this->checkSeo(),
|
||||
'template' => $this->checkTemplate(),
|
||||
'config' => $this->checkConfigDrift(),
|
||||
];
|
||||
|
||||
return $checks;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1310,55 +1232,12 @@ class MokoWaaS extends CMSPlugin
|
||||
$status = 'degraded';
|
||||
}
|
||||
|
||||
// Total disk and site size
|
||||
$totalBytes = @disk_total_space(JPATH_ROOT);
|
||||
$totalMb = $totalBytes !== false
|
||||
? round($totalBytes / 1048576)
|
||||
: null;
|
||||
|
||||
// Site directory size (quick estimate via common dirs)
|
||||
$siteMb = null;
|
||||
|
||||
try
|
||||
{
|
||||
$siteSize = 0;
|
||||
|
||||
foreach (['images', 'media', 'tmp', 'cache',
|
||||
'administrator/logs', 'administrator/cache'] as $dir)
|
||||
{
|
||||
$path = JPATH_ROOT . '/' . $dir;
|
||||
|
||||
if (is_dir($path))
|
||||
{
|
||||
$iter = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator(
|
||||
$path,
|
||||
\FilesystemIterator::SKIP_DOTS
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($iter as $file)
|
||||
{
|
||||
$siteSize += $file->getSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$siteMb = round($siteSize / 1048576);
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
// Ignore — siteMb stays null
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'tmp_writable' => $tmpWritable,
|
||||
'log_writable' => $logWritable,
|
||||
'cache_writable' => $cacheWritable,
|
||||
'free_disk_mb' => $freeMb,
|
||||
'total_disk_mb' => $totalMb,
|
||||
'site_size_mb' => $siteMb,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1441,774 +1320,6 @@ class MokoWaaS extends CMSPlugin
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Akeeba Backup status — last backup date, status, and profile.
|
||||
*
|
||||
* Queries the #__ak_stats table (Akeeba Backup) for the most recent
|
||||
* backup record. Returns 'not_installed' if the table doesn't exist.
|
||||
*
|
||||
* @return array Check result with backup info
|
||||
*
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkAkeebaBackup()
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Check if Akeeba Backup is installed
|
||||
$tables = $db->getTableList();
|
||||
$prefix = $db->getPrefix();
|
||||
$akTable = $prefix . 'ak_stats';
|
||||
|
||||
if (!in_array($akTable, $tables))
|
||||
{
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'installed' => false,
|
||||
];
|
||||
}
|
||||
|
||||
// Get the most recent backup
|
||||
$query = $db->getQuery(true)
|
||||
->select([
|
||||
$db->quoteName('id'),
|
||||
$db->quoteName('description'),
|
||||
$db->quoteName('status'),
|
||||
$db->quoteName('backupstart'),
|
||||
$db->quoteName('backupend'),
|
||||
$db->quoteName('profile_id'),
|
||||
$db->quoteName('total_size'),
|
||||
])
|
||||
->from($db->quoteName('#__ak_stats'))
|
||||
->order($db->quoteName('id') . ' DESC');
|
||||
|
||||
$db->setQuery($query, 0, 1);
|
||||
$latest = $db->loadObject();
|
||||
|
||||
if (!$latest)
|
||||
{
|
||||
return [
|
||||
'status' => 'degraded',
|
||||
'installed' => true,
|
||||
'message' => 'No backups found',
|
||||
];
|
||||
}
|
||||
|
||||
// Count total backups and recent (last 7 days)
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__ak_stats'))
|
||||
);
|
||||
$totalBackups = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__ak_stats'))
|
||||
->where($db->quoteName('backupstart')
|
||||
. ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)')
|
||||
);
|
||||
$recentBackups = (int) $db->loadResult();
|
||||
|
||||
// Check if last backup is older than 7 days
|
||||
$lastDate = $latest->backupstart;
|
||||
$daysSince = (int) ((time() - strtotime($lastDate)) / 86400);
|
||||
$backupSize = $latest->total_size
|
||||
? round($latest->total_size / 1048576)
|
||||
: null;
|
||||
|
||||
$status = 'ok';
|
||||
|
||||
if ($latest->status !== 'complete')
|
||||
{
|
||||
$status = 'degraded';
|
||||
}
|
||||
elseif ($daysSince > 7)
|
||||
{
|
||||
$status = 'degraded';
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'installed' => true,
|
||||
'last_backup' => $lastDate,
|
||||
'last_status' => $latest->status,
|
||||
'last_size_mb' => $backupSize,
|
||||
'days_since' => $daysSince,
|
||||
'profile_id' => (int) $latest->profile_id,
|
||||
'total_backups' => $totalBackups,
|
||||
'recent_7d' => $recentBackups,
|
||||
'description' => $latest->description,
|
||||
];
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'installed' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Admin Tools status — WAF status, security exceptions.
|
||||
*
|
||||
* Queries Admin Tools tables for firewall status and recent blocks.
|
||||
* Returns 'not_installed' if tables don't exist.
|
||||
*
|
||||
* @return array Check result with security info
|
||||
*
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkAdminTools()
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$tables = $db->getTableList();
|
||||
$prefix = $db->getPrefix();
|
||||
|
||||
// Check if Admin Tools is installed
|
||||
$atTable = $prefix . 'admintools_log';
|
||||
|
||||
if (!in_array($atTable, $tables))
|
||||
{
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'installed' => false,
|
||||
];
|
||||
}
|
||||
|
||||
// Count blocked requests in last 24h
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__admintools_log'))
|
||||
->where($db->quoteName('logdate')
|
||||
. ' >= DATE_SUB(NOW(), INTERVAL 1 DAY)')
|
||||
);
|
||||
$blocked24h = (int) $db->loadResult();
|
||||
|
||||
// Count blocked in last 7 days
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__admintools_log'))
|
||||
->where($db->quoteName('logdate')
|
||||
. ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)')
|
||||
);
|
||||
$blocked7d = (int) $db->loadResult();
|
||||
|
||||
// Check WAF config if available
|
||||
$wafEnabled = null;
|
||||
$wafTable = $prefix . 'admintools_wafconfig';
|
||||
|
||||
if (in_array($wafTable, $tables))
|
||||
{
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select($db->quoteName('value'))
|
||||
->from($db->quoteName('#__admintools_wafconfig'))
|
||||
->where($db->quoteName('key') . ' = '
|
||||
. $db->quote('ipworkarounds'))
|
||||
);
|
||||
$wafEnabled = $db->loadResult() !== null;
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'installed' => true,
|
||||
'blocked_24h' => $blocked24h,
|
||||
'blocked_7d' => $blocked7d,
|
||||
'waf_active' => $wafEnabled,
|
||||
];
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'installed' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check SSL certificate expiry.
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkSsl()
|
||||
{
|
||||
try
|
||||
{
|
||||
$siteUrl = Uri::root();
|
||||
$host = parse_url($siteUrl, PHP_URL_HOST);
|
||||
|
||||
if (empty($host) || parse_url($siteUrl, PHP_URL_SCHEME) !== 'https')
|
||||
{
|
||||
return ['status' => 'ok', 'https' => false];
|
||||
}
|
||||
|
||||
$ctx = stream_context_create([
|
||||
'ssl' => ['capture_peer_cert' => true, 'verify_peer' => false],
|
||||
]);
|
||||
$stream = @stream_socket_client(
|
||||
"ssl://{$host}:443", $errno, $errstr, 10,
|
||||
STREAM_CLIENT_CONNECT, $ctx
|
||||
);
|
||||
|
||||
if (!$stream)
|
||||
{
|
||||
return ['status' => 'degraded', 'https' => true, 'message' => 'Cannot connect'];
|
||||
}
|
||||
|
||||
$params = stream_context_get_params($stream);
|
||||
$cert = openssl_x509_parse($params['options']['ssl']['peer_certificate']);
|
||||
fclose($stream);
|
||||
|
||||
$expiresTs = $cert['validTo_time_t'] ?? 0;
|
||||
$daysLeft = (int) (($expiresTs - time()) / 86400);
|
||||
$issuer = $cert['issuer']['O'] ?? $cert['issuer']['CN'] ?? 'Unknown';
|
||||
$status = $daysLeft < 7 ? 'error' : ($daysLeft < 30 ? 'degraded' : 'ok');
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'https' => true,
|
||||
'expires' => gmdate('Y-m-d', $expiresTs),
|
||||
'days_left' => $daysLeft,
|
||||
'issuer' => $issuer,
|
||||
];
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return ['status' => 'ok', 'https' => false];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Joomla scheduled tasks (Joomla 4.1+).
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkScheduledTasks()
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$tables = $db->getTableList();
|
||||
$prefix = $db->getPrefix();
|
||||
|
||||
if (!in_array($prefix . 'scheduler_tasks', $tables))
|
||||
{
|
||||
return ['status' => 'ok', 'available' => false];
|
||||
}
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__scheduler_tasks'))
|
||||
->where($db->quoteName('state') . ' = 1')
|
||||
);
|
||||
$enabled = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select([
|
||||
$db->quoteName('title'),
|
||||
$db->quoteName('last_execution'),
|
||||
$db->quoteName('last_exit_code'),
|
||||
$db->quoteName('next_execution'),
|
||||
])
|
||||
->from($db->quoteName('#__scheduler_tasks'))
|
||||
->where($db->quoteName('state') . ' = 1')
|
||||
->order($db->quoteName('last_execution') . ' DESC')
|
||||
);
|
||||
$db->setQuery($db->getQuery(true), 0, 5);
|
||||
// Re-run the query
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select([
|
||||
$db->quoteName('title'),
|
||||
$db->quoteName('last_execution'),
|
||||
$db->quoteName('last_exit_code'),
|
||||
$db->quoteName('next_execution'),
|
||||
])
|
||||
->from($db->quoteName('#__scheduler_tasks'))
|
||||
->where($db->quoteName('state') . ' = 1')
|
||||
->order($db->quoteName('last_execution') . ' DESC'),
|
||||
0, 1
|
||||
);
|
||||
$last = $db->loadObject();
|
||||
|
||||
// Count failed in last 24h
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__scheduler_tasks'))
|
||||
->where($db->quoteName('last_exit_code') . ' != 0')
|
||||
->where($db->quoteName('last_execution')
|
||||
. ' >= DATE_SUB(NOW(), INTERVAL 1 DAY)')
|
||||
);
|
||||
$failed24h = (int) $db->loadResult();
|
||||
|
||||
$status = $failed24h > 0 ? 'degraded' : 'ok';
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'available' => true,
|
||||
'enabled_tasks' => $enabled,
|
||||
'failed_24h' => $failed24h,
|
||||
'last_run' => $last->last_execution ?? null,
|
||||
'last_exit_code' => $last ? (int) $last->last_exit_code : null,
|
||||
'last_task' => $last->title ?? null,
|
||||
];
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return ['status' => 'ok', 'available' => false];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check PHP error log for recent errors.
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkErrorLog()
|
||||
{
|
||||
$logFile = JPATH_ROOT . '/administrator/logs/error.php';
|
||||
$altLog = ini_get('error_log');
|
||||
|
||||
$file = null;
|
||||
|
||||
if (file_exists($logFile) && is_readable($logFile))
|
||||
{
|
||||
$file = $logFile;
|
||||
}
|
||||
elseif ($altLog && file_exists($altLog) && is_readable($altLog))
|
||||
{
|
||||
$file = $altLog;
|
||||
}
|
||||
|
||||
if (!$file)
|
||||
{
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'log_available' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$size = filesize($file);
|
||||
$sizeMb = round($size / 1048576, 1);
|
||||
|
||||
// Count recent lines (tail last 50 lines, count errors)
|
||||
$lines = file_exists($file) ? @file($file) : [];
|
||||
$recent = array_slice($lines, -50);
|
||||
$errors24h = 0;
|
||||
$lastError = null;
|
||||
$yesterday = date('Y-m-d', strtotime('-1 day'));
|
||||
|
||||
foreach ($recent as $line)
|
||||
{
|
||||
if (stripos($line, 'error') !== false
|
||||
|| stripos($line, 'fatal') !== false)
|
||||
{
|
||||
$errors24h++;
|
||||
$lastError = trim(substr($line, 0, 200));
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'log_available' => true,
|
||||
'log_size_mb' => $sizeMb,
|
||||
'recent_errors' => $errors24h,
|
||||
'last_error' => $lastError,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check database size and largest tables.
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkDatabaseSize()
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$config = Factory::getConfig();
|
||||
$dbName = $config->get('db');
|
||||
|
||||
$db->setQuery(
|
||||
"SELECT ROUND(SUM(data_length + index_length) / 1048576, 1) AS size_mb "
|
||||
. "FROM information_schema.tables WHERE table_schema = "
|
||||
. $db->quote($dbName)
|
||||
);
|
||||
$totalMb = (float) $db->loadResult();
|
||||
|
||||
// Largest tables
|
||||
$db->setQuery(
|
||||
"SELECT table_name, "
|
||||
. "ROUND((data_length + index_length) / 1048576, 1) AS size_mb "
|
||||
. "FROM information_schema.tables "
|
||||
. "WHERE table_schema = " . $db->quote($dbName)
|
||||
. " ORDER BY (data_length + index_length) DESC LIMIT 5"
|
||||
);
|
||||
$largest = [];
|
||||
|
||||
foreach ($db->loadObjectList() as $t)
|
||||
{
|
||||
$largest[$t->table_name] = (float) $t->size_mb;
|
||||
}
|
||||
|
||||
// Table count
|
||||
$db->setQuery(
|
||||
"SELECT COUNT(*) FROM information_schema.tables "
|
||||
. "WHERE table_schema = " . $db->quote($dbName)
|
||||
);
|
||||
$tableCount = (int) $db->loadResult();
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'total_mb' => $totalMb,
|
||||
'table_count' => $tableCount,
|
||||
'largest' => $largest,
|
||||
];
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return ['status' => 'ok', 'total_mb' => null];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check content statistics.
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkContent()
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$counts = [];
|
||||
|
||||
foreach ([
|
||||
'articles' => '#__content',
|
||||
'categories' => '#__categories',
|
||||
'menu_items' => '#__menu',
|
||||
'modules' => '#__modules',
|
||||
'media' => '#__media_files',
|
||||
] as $label => $table)
|
||||
{
|
||||
try
|
||||
{
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName($table))
|
||||
);
|
||||
$counts[$label] = (int) $db->loadResult();
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
// Table might not exist
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'counts' => $counts,
|
||||
];
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return ['status' => 'ok', 'counts' => []];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user activity — last login, active sessions, failed logins.
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkUserActivity()
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Total users
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__users'))
|
||||
);
|
||||
$totalUsers = (int) $db->loadResult();
|
||||
|
||||
// Last login
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select($db->quoteName('lastvisitDate'))
|
||||
->from($db->quoteName('#__users'))
|
||||
->where($db->quoteName('lastvisitDate')
|
||||
. ' IS NOT NULL')
|
||||
->order($db->quoteName('lastvisitDate') . ' DESC'),
|
||||
0, 1
|
||||
);
|
||||
$lastLogin = $db->loadResult();
|
||||
|
||||
// Active sessions
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__session'))
|
||||
->where($db->quoteName('guest') . ' = 0')
|
||||
);
|
||||
$activeSessions = (int) $db->loadResult();
|
||||
|
||||
// Failed logins (from action logs if available)
|
||||
$failedLogins = 0;
|
||||
|
||||
try
|
||||
{
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__action_logs'))
|
||||
->where($db->quoteName('message_language_key')
|
||||
. ' LIKE ' . $db->quote('%LOGIN_FAILED%'))
|
||||
->where($db->quoteName('log_date')
|
||||
. ' >= DATE_SUB(NOW(), INTERVAL 1 DAY)')
|
||||
);
|
||||
$failedLogins = (int) $db->loadResult();
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
// Action logs might not track this
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'total_users' => $totalUsers,
|
||||
'last_login' => $lastLogin,
|
||||
'active_sessions' => $activeSessions,
|
||||
'failed_24h' => $failedLogins,
|
||||
];
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return ['status' => 'ok', 'total_users' => null];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check mail system status.
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkMail()
|
||||
{
|
||||
try
|
||||
{
|
||||
$config = Factory::getConfig();
|
||||
$mailer = $config->get('mailer', 'mail');
|
||||
$from = $config->get('mailfrom', '');
|
||||
$smtpHost = $config->get('smtphost', '');
|
||||
|
||||
// Check mail queue if available
|
||||
$db = Factory::getDbo();
|
||||
$tables = $db->getTableList();
|
||||
$prefix = $db->getPrefix();
|
||||
|
||||
$queueCount = 0;
|
||||
|
||||
if (in_array($prefix . 'mail_queue', $tables))
|
||||
{
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__mail_queue'))
|
||||
);
|
||||
$queueCount = (int) $db->loadResult();
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'mailer' => $mailer,
|
||||
'from' => $from,
|
||||
'smtp_host' => $mailer === 'smtp' ? $smtpHost : null,
|
||||
'queue' => $queueCount,
|
||||
];
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return ['status' => 'ok', 'mailer' => null];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check basic SEO health indicators.
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkSeo()
|
||||
{
|
||||
$robotsTxt = file_exists(JPATH_ROOT . '/robots.txt');
|
||||
$htaccess = file_exists(JPATH_ROOT . '/.htaccess');
|
||||
|
||||
// Check for sitemap
|
||||
$sitemapXml = file_exists(JPATH_ROOT . '/sitemap.xml');
|
||||
$sitemapIdx = file_exists(JPATH_ROOT . '/sitemap_index.xml');
|
||||
|
||||
$config = Factory::getConfig();
|
||||
$sef = (bool) $config->get('sef', 0);
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'robots_txt' => $robotsTxt,
|
||||
'htaccess' => $htaccess,
|
||||
'sitemap' => $sitemapXml || $sitemapIdx,
|
||||
'sef_enabled' => $sef,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check active template info.
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkTemplate()
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Site template
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select($db->quoteName('template'))
|
||||
->from($db->quoteName('#__template_styles'))
|
||||
->where($db->quoteName('client_id') . ' = 0')
|
||||
->where($db->quoteName('home') . ' = 1')
|
||||
);
|
||||
$siteTemplate = $db->loadResult() ?: 'unknown';
|
||||
|
||||
// Admin template
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select($db->quoteName('template'))
|
||||
->from($db->quoteName('#__template_styles'))
|
||||
->where($db->quoteName('client_id') . ' = 1')
|
||||
->where($db->quoteName('home') . ' = 1')
|
||||
);
|
||||
$adminTemplate = $db->loadResult() ?: 'unknown';
|
||||
|
||||
// Count template overrides
|
||||
$overrideCount = 0;
|
||||
$overridePath = JPATH_ROOT . '/templates/' . $siteTemplate . '/html';
|
||||
|
||||
if (is_dir($overridePath))
|
||||
{
|
||||
$iter = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator(
|
||||
$overridePath,
|
||||
\FilesystemIterator::SKIP_DOTS
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($iter as $file)
|
||||
{
|
||||
if ($file->isFile())
|
||||
{
|
||||
$overrideCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'site_template' => $siteTemplate,
|
||||
'admin_template' => $adminTemplate,
|
||||
'override_count' => $overrideCount,
|
||||
];
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return ['status' => 'ok', 'site_template' => null];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check configuration for common misconfigurations.
|
||||
*
|
||||
* @return array
|
||||
* @since 02.01.39
|
||||
*/
|
||||
protected function checkConfigDrift()
|
||||
{
|
||||
$config = Factory::getConfig();
|
||||
|
||||
$debug = (bool) $config->get('debug', 0);
|
||||
$errorReport = $config->get('error_reporting', 'default');
|
||||
$gzip = (bool) $config->get('gzip', 0);
|
||||
$sef = (bool) $config->get('sef', 0);
|
||||
$sefRewrite = (bool) $config->get('sef_rewrite', 0);
|
||||
$forceSSL = (int) $config->get('force_ssl', 0);
|
||||
$caching = (bool) $config->get('caching', 0);
|
||||
$lifetime = (int) $config->get('lifetime', 15);
|
||||
$tmpPath = $config->get('tmp_path', '');
|
||||
$logPath = $config->get('log_path', '');
|
||||
|
||||
// Flag potential issues
|
||||
$issues = [];
|
||||
|
||||
if ($debug)
|
||||
{
|
||||
$issues[] = 'Debug mode is ON';
|
||||
}
|
||||
|
||||
if ($errorReport === 'maximum'
|
||||
|| $errorReport === 'development')
|
||||
{
|
||||
$issues[] = 'Error reporting: ' . $errorReport;
|
||||
}
|
||||
|
||||
if ($forceSSL === 0)
|
||||
{
|
||||
$issues[] = 'Force SSL is OFF';
|
||||
}
|
||||
|
||||
$status = empty($issues) ? 'ok' : 'degraded';
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'debug' => $debug,
|
||||
'error_report' => $errorReport,
|
||||
'gzip' => $gzip,
|
||||
'sef' => $sef,
|
||||
'sef_rewrite' => $sefRewrite,
|
||||
'force_ssl' => $forceSSL,
|
||||
'caching' => $caching,
|
||||
'lifetime' => $lifetime,
|
||||
'issues' => $issues ?: null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON health response and terminate execution.
|
||||
*
|
||||
|
||||
+76
-4
@@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
VERSION: 02.01.36-dev
|
||||
VERSION: 02.01.37
|
||||
-->
|
||||
|
||||
<updates>
|
||||
@@ -10,15 +10,87 @@
|
||||
<description>System - MokoWaaS update</description>
|
||||
<element>mokowaas</element>
|
||||
<type>plugin</type>
|
||||
<version>02.01.36-dev</version>
|
||||
<version>02.01.37-dev</version>
|
||||
<client>site</client>
|
||||
<folder>system</folder>
|
||||
<tags><tag>development</tag></tags>
|
||||
<infourl title="System - MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/plg_system_mokowaas-02.01.36-dev.zip</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/plg_system_mokowaas-02.01.37-dev.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>9f7e0df50d0073ecb69ba3c13c22aec07b584e2f09127fc36bcf7eeeef534481</sha256>
|
||||
<sha256>9d71440040a957fb155c9951ea76ec9e684dca99565be8ff2c0acf5d1a3a5edd</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>System - MokoWaaS</name>
|
||||
<description>System - MokoWaaS update</description>
|
||||
<element>mokowaas</element>
|
||||
<type>plugin</type>
|
||||
<version>02.01.37-alpha</version>
|
||||
<client>site</client>
|
||||
<folder>system</folder>
|
||||
<tags><tag>alpha</tag></tags>
|
||||
<infourl title="System - MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/alpha</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/alpha/plg_system_mokowaas-02.01.37-alpha.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>9d71440040a957fb155c9951ea76ec9e684dca99565be8ff2c0acf5d1a3a5edd</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>System - MokoWaaS</name>
|
||||
<description>System - MokoWaaS update</description>
|
||||
<element>mokowaas</element>
|
||||
<type>plugin</type>
|
||||
<version>02.01.37-beta</version>
|
||||
<client>site</client>
|
||||
<folder>system</folder>
|
||||
<tags><tag>beta</tag></tags>
|
||||
<infourl title="System - MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/beta</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/beta/plg_system_mokowaas-02.01.37-beta.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>9d71440040a957fb155c9951ea76ec9e684dca99565be8ff2c0acf5d1a3a5edd</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>System - MokoWaaS</name>
|
||||
<description>System - MokoWaaS update</description>
|
||||
<element>mokowaas</element>
|
||||
<type>plugin</type>
|
||||
<version>02.01.37-rc</version>
|
||||
<client>site</client>
|
||||
<folder>system</folder>
|
||||
<tags><tag>rc</tag></tags>
|
||||
<infourl title="System - MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/rc</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/rc/plg_system_mokowaas-02.01.37-rc.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>9d71440040a957fb155c9951ea76ec9e684dca99565be8ff2c0acf5d1a3a5edd</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>System - MokoWaaS</name>
|
||||
<description>System - MokoWaaS update</description>
|
||||
<element>mokowaas</element>
|
||||
<type>plugin</type>
|
||||
<version>02.01.37</version>
|
||||
<client>site</client>
|
||||
<folder>system</folder>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<infourl title="System - MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/plg_system_mokowaas-02.01.37.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>9d71440040a957fb155c9951ea76ec9e684dca99565be8ff2c0acf5d1a3a5edd</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
|
||||
Reference in New Issue
Block a user