Revert "Merge remote-tracking branch 'origin/dev'"
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
This reverts commitb27ef3aee3, reversing changes made toc4d8381828.
This commit is contained in:
@@ -15,7 +15,7 @@
|
|||||||
DEFGROUP: Joomla.Plugin
|
DEFGROUP: Joomla.Plugin
|
||||||
INGROUP: MokoWaaS
|
INGROUP: MokoWaaS
|
||||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||||
VERSION: 02.01.37
|
VERSION: 02.01.35
|
||||||
PATH: /README.md
|
PATH: /README.md
|
||||||
BRIEF: Rebranding plugin for MokoWaaS platform
|
BRIEF: Rebranding plugin for MokoWaaS platform
|
||||||
NOTE: Internal WaaS identity abstraction layer
|
NOTE: Internal WaaS identity abstraction layer
|
||||||
|
|||||||
+8
-897
@@ -1090,89 +1090,25 @@ class MokoWaaS extends CMSPlugin
|
|||||||
// Collect diagnostics
|
// Collect diagnostics
|
||||||
$checks = $this->collectHealthChecks();
|
$checks = $this->collectHealthChecks();
|
||||||
|
|
||||||
// Determine overall status and collect reasons
|
// Determine overall status from individual checks
|
||||||
$overall = 'ok';
|
$overall = 'ok';
|
||||||
$reasons = [];
|
|
||||||
|
|
||||||
foreach ($checks as $name => $check)
|
foreach ($checks as $check)
|
||||||
{
|
{
|
||||||
$checkStatus = $check['status'] ?? 'ok';
|
if (($check['status'] ?? 'ok') === 'error')
|
||||||
|
|
||||||
if ($checkStatus === 'error')
|
|
||||||
{
|
{
|
||||||
$overall = 'error';
|
$overall = 'error';
|
||||||
$reasons[] = $name . ': ' . ($check['message'] ?? 'error');
|
break;
|
||||||
}
|
}
|
||||||
elseif ($checkStatus === 'degraded')
|
|
||||||
{
|
|
||||||
if ($overall !== 'error')
|
|
||||||
{
|
|
||||||
$overall = 'degraded';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build human-readable reason
|
if (($check['status'] ?? 'ok') === 'degraded')
|
||||||
if ($name === 'extensions'
|
{
|
||||||
&& isset($check['pending_updates']))
|
$overall = 'degraded';
|
||||||
{
|
|
||||||
$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';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
'status' => $overall,
|
'status' => $overall,
|
||||||
'reason' => implode('; ', $reasons) ?: null,
|
|
||||||
'timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
|
'timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
|
||||||
'checks' => $checks,
|
'checks' => $checks,
|
||||||
'meta' => $this->collectHealthMeta(),
|
'meta' => $this->collectHealthMeta(),
|
||||||
@@ -1193,26 +1129,12 @@ class MokoWaaS extends CMSPlugin
|
|||||||
*/
|
*/
|
||||||
protected function collectHealthChecks()
|
protected function collectHealthChecks()
|
||||||
{
|
{
|
||||||
$checks = [
|
return [
|
||||||
'database' => $this->checkDatabase(),
|
'database' => $this->checkDatabase(),
|
||||||
'filesystem' => $this->checkFilesystem(),
|
'filesystem' => $this->checkFilesystem(),
|
||||||
'cache' => $this->checkCache(),
|
'cache' => $this->checkCache(),
|
||||||
'extensions' => $this->checkExtensions(),
|
'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';
|
$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 [
|
return [
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
'tmp_writable' => $tmpWritable,
|
'tmp_writable' => $tmpWritable,
|
||||||
'log_writable' => $logWritable,
|
'log_writable' => $logWritable,
|
||||||
'cache_writable' => $cacheWritable,
|
'cache_writable' => $cacheWritable,
|
||||||
'free_disk_mb' => $freeMb,
|
'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.
|
* Send a JSON health response and terminate execution.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user