* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @license GNU General Public License version 3 or later; see LICENSE */ namespace Joomla\Component\MokoSuiteBackup\Administrator\Utility; defined('_JEXEC') or die; use Joomla\CMS\Factory; use Joomla\Database\DatabaseInterface; /** * Provides a public API for external plugins (e.g. MokoSuiteClient bridge) * to query backup status without depending on internal model internals. * * @since 01.22.07 */ class BackupStatusHelper { /** * Get a summary of the latest backup status. * * Returns an array suitable for inclusion in heartbeat payloads. * * @param int $staleDays Days without backup before status is degraded. * * @return array */ public static function getStatus(int $staleDays = 7): array { try { $db = Factory::getContainer()->get(DatabaseInterface::class); } catch (\Throwable $e) { return ['installed' => true, 'status' => 'error', 'message' => 'Database unavailable']; } // Most recently inserted backup record (by ID, any status) $query = $db->getQuery(true) ->select([ $db->quoteName('id'), $db->quoteName('description'), $db->quoteName('status'), $db->quoteName('backup_type'), $db->quoteName('total_size'), $db->quoteName('backupstart'), $db->quoteName('backupend'), $db->quoteName('origin'), $db->quoteName('filesexist'), ]) ->from($db->quoteName('#__mokosuitebackup_records')) ->order($db->quoteName('id') . ' DESC'); $db->setQuery($query, 0, 1); $latest = $db->loadObject(); if (!$latest) { return [ 'installed' => true, 'status' => 'degraded', 'message' => 'No backups found', ]; } $db->setQuery( $db->getQuery(true) ->select('COUNT(*)') ->from($db->quoteName('#__mokosuitebackup_records')) ->where($db->quoteName('status') . ' = ' . $db->quote('complete')) ); $totalBackups = (int) $db->loadResult(); $cutoff = date('Y-m-d H:i:s', strtotime("-{$staleDays} days")); $db->setQuery( $db->getQuery(true) ->select('COUNT(*)') ->from($db->quoteName('#__mokosuitebackup_records')) ->where($db->quoteName('status') . ' = ' . $db->quote('complete')) ->where($db->quoteName('backupstart') . ' >= ' . $db->quote($cutoff)) ); $recentBackups = (int) $db->loadResult(); // Failures in last 7 days $db->setQuery( $db->getQuery(true) ->select('COUNT(*)') ->from($db->quoteName('#__mokosuitebackup_records')) ->where($db->quoteName('status') . ' = ' . $db->quote('fail')) ->where($db->quoteName('backupstart') . ' >= ' . $db->quote($cutoff)) ); $failCount7d = (int) $db->loadResult(); // Determine overall status $daysSince = 999; if (!empty($latest->backupstart) && $latest->backupstart !== '0000-00-00 00:00:00') { $daysSince = (int) ((time() - strtotime($latest->backupstart)) / 86400); } $status = 'ok'; if ($latest->status === 'fail') { $status = 'degraded'; } elseif ($latest->status !== 'complete') { $status = ($latest->status === 'running') ? 'ok' : 'degraded'; } elseif ($daysSince > $staleDays) { $status = 'degraded'; } $sizeMb = $latest->total_size ? round($latest->total_size / 1048576) : null; return [ 'installed' => true, 'status' => $status, 'last_backup' => $latest->backupstart, 'last_status' => $latest->status, 'last_size_mb' => $sizeMb, 'days_since' => $daysSince, 'backup_type' => $latest->backup_type, 'origin' => $latest->origin, 'total_backups' => $totalBackups, 'recent_7d' => $recentBackups, 'fail_count_7d' => $failCount7d, 'files_exist' => (bool) $latest->filesexist, 'description' => $latest->description, ]; } /** * Check if MokoSuiteBackup component is installed. * * Useful for external plugins that want to check before calling getStatus(). * * @return bool */ public static function isInstalled(): bool { try { $db = Factory::getContainer()->get(DatabaseInterface::class); $query = $db->getQuery(true) ->select('COUNT(*)') ->from($db->quoteName('#__extensions')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuitebackup')) ->where($db->quoteName('type') . ' = ' . $db->quote('component')); $db->setQuery($query); return (int) $db->loadResult() > 0; } catch (\Throwable $e) { return false; } } }