a5a2f48e7c
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: Auto Version Bump / Version Bump (push) Successful in 15s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 35s
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
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (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
Static helper class for external consumers (bridge plugins, MCP servers) to query backup status without bootstrapping the full component. Methods: - isInstalled(): check if MokoSuiteBackup is installed and enabled - getLatestRecord(): get the most recent completed/failed backup - getStatusSummary(): full heartbeat payload with latest status, all-time/7-day totals, and consecutive success streak
181 lines
6.9 KiB
PHP
181 lines
6.9 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\Helper;
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
|
|
/**
|
|
* Lightweight helper for external consumers (bridge plugins, MCP servers, CLI tools)
|
|
* to query MokoSuiteBackup status without bootstrapping the full component.
|
|
*
|
|
* Usage from any Joomla plugin:
|
|
* \Joomla\Component\MokoSuiteBackup\Administrator\Helper\BackupStatusHelper::getStatusSummary()
|
|
*/
|
|
class BackupStatusHelper
|
|
{
|
|
/**
|
|
* Check whether MokoSuiteBackup is installed and enabled.
|
|
*/
|
|
public static function isInstalled(): bool
|
|
{
|
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
|
|
|
$query = $db->getQuery(true)
|
|
->select('COUNT(*)')
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('pkg_mokosuitebackup'))
|
|
->where($db->quoteName('enabled') . ' = 1');
|
|
|
|
return (int) $db->setQuery($query)->loadResult() > 0;
|
|
}
|
|
|
|
/**
|
|
* Get the latest backup record for a given profile (or any profile).
|
|
*
|
|
* @param int|null $profileId Limit to a specific profile, or null for any.
|
|
* @return object|null Record object or null if no backups exist.
|
|
*/
|
|
public static function getLatestRecord(?int $profileId = null): ?object
|
|
{
|
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
|
$query = $db->getQuery(true)
|
|
->select([
|
|
$db->quoteName('r.id'),
|
|
$db->quoteName('r.profile_id'),
|
|
$db->quoteName('r.description'),
|
|
$db->quoteName('r.status'),
|
|
$db->quoteName('r.origin'),
|
|
$db->quoteName('r.backup_type'),
|
|
$db->quoteName('r.archivename'),
|
|
$db->quoteName('r.total_size'),
|
|
$db->quoteName('r.db_size'),
|
|
$db->quoteName('r.files_count'),
|
|
$db->quoteName('r.tables_count'),
|
|
$db->quoteName('r.backupstart'),
|
|
$db->quoteName('r.backupend'),
|
|
$db->quoteName('r.filesexist'),
|
|
$db->quoteName('r.remote_filename'),
|
|
$db->quoteName('r.checksum'),
|
|
$db->quoteName('p.title', 'profile_title'),
|
|
])
|
|
->from($db->quoteName('#__mokosuitebackup_records', 'r'))
|
|
->join('LEFT', $db->quoteName('#__mokosuitebackup_profiles', 'p') . ' ON p.id = r.profile_id')
|
|
->where($db->quoteName('r.status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'fail'])) . ')')
|
|
->order($db->quoteName('r.backupstart') . ' DESC');
|
|
|
|
if ($profileId !== null) {
|
|
$query->where($db->quoteName('r.profile_id') . ' = ' . (int) $profileId);
|
|
}
|
|
|
|
$query->setLimit(1);
|
|
$record = $db->setQuery($query)->loadObject();
|
|
|
|
return $record ?: null;
|
|
}
|
|
|
|
/**
|
|
* Get a full status summary for heartbeat payloads.
|
|
*
|
|
* Returns an array suitable for JSON encoding in bridge heartbeats:
|
|
* - latest backup status, time, size, destination
|
|
* - total and recent (7-day) backup counts
|
|
* - streak of consecutive successes
|
|
*
|
|
* @return array{installed: bool, latest: ?array, totals: array}
|
|
*/
|
|
public static function getStatusSummary(): array
|
|
{
|
|
if (!self::isInstalled()) {
|
|
return ['installed' => false, 'latest' => null, 'totals' => []];
|
|
}
|
|
|
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
|
|
|
// Latest completed/failed backup
|
|
$latest = self::getLatestRecord();
|
|
$latestArray = null;
|
|
|
|
if ($latest) {
|
|
$latestArray = [
|
|
'status' => $latest->status,
|
|
'backup_type' => $latest->backup_type,
|
|
'description' => $latest->description,
|
|
'backup_start' => $latest->backupstart,
|
|
'backup_end' => $latest->backupend,
|
|
'total_size' => (int) $latest->total_size,
|
|
'destination' => $latest->remote_filename ? 'remote' : 'local',
|
|
'profile' => $latest->profile_title,
|
|
'origin' => $latest->origin,
|
|
'files_count' => (int) $latest->files_count,
|
|
'tables_count' => (int) $latest->tables_count,
|
|
];
|
|
}
|
|
|
|
// Totals
|
|
$query = $db->getQuery(true)
|
|
->select([
|
|
'COUNT(*) AS total',
|
|
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('complete') . ' THEN 1 ELSE 0 END) AS success',
|
|
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('fail') . ' THEN 1 ELSE 0 END) AS failed',
|
|
])
|
|
->from($db->quoteName('#__mokosuitebackup_records'));
|
|
|
|
$allTime = $db->setQuery($query)->loadObject();
|
|
|
|
// Recent (last 7 days)
|
|
$query = $db->getQuery(true)
|
|
->select([
|
|
'COUNT(*) AS total',
|
|
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('complete') . ' THEN 1 ELSE 0 END) AS success',
|
|
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('fail') . ' THEN 1 ELSE 0 END) AS failed',
|
|
])
|
|
->from($db->quoteName('#__mokosuitebackup_records'))
|
|
->where($db->quoteName('backupstart') . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)');
|
|
|
|
$recent = $db->setQuery($query)->loadObject();
|
|
|
|
// Success streak — count consecutive successes from latest backward
|
|
$query = $db->getQuery(true)
|
|
->select($db->quoteName('status'))
|
|
->from($db->quoteName('#__mokosuitebackup_records'))
|
|
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'fail'])) . ')')
|
|
->order($db->quoteName('backupstart') . ' DESC')
|
|
->setLimit(50);
|
|
|
|
$statuses = $db->setQuery($query)->loadColumn();
|
|
$streak = 0;
|
|
|
|
foreach ($statuses as $s) {
|
|
if ($s === 'complete') {
|
|
$streak++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'installed' => true,
|
|
'latest' => $latestArray,
|
|
'totals' => [
|
|
'all_time' => (int) ($allTime->total ?? 0),
|
|
'all_success' => (int) ($allTime->success ?? 0),
|
|
'all_failed' => (int) ($allTime->failed ?? 0),
|
|
'recent_total' => (int) ($recent->total ?? 0),
|
|
'recent_success' => (int) ($recent->success ?? 0),
|
|
'recent_failed' => (int) ($recent->failed ?? 0),
|
|
'success_streak' => $streak,
|
|
],
|
|
];
|
|
}
|
|
}
|