a77458a24a
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 26s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
- Use $staleDays instead of hardcoded 7-day cutoff for recent/failure counts so the parameter is consistently applied - Remove unused profile_id from SELECT (never included in return array)
174 lines
4.5 KiB
PHP
174 lines
4.5 KiB
PHP
<?php
|
|
/**
|
|
* @package MokoSuiteBackup
|
|
* @subpackage com_mokosuitebackup
|
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
|
* @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;
|
|
}
|
|
}
|
|
}
|