Files
MokoSuiteBackup/source/packages/com_mokosuitebackup/src/Service/BackupRunner.php
T
jmiller 307067580c feat: BackupRunner helper — unify backup run/status/notification
Every non-interactive backup trigger used a slightly different path, so
final status and notification gating behaved inconsistently depending on
what started the backup (this is why pre-update backups did not gate
notifications the way "Backup Now" does).

Add src/Service/BackupRunner.php as the single synchronous entry point.
It delegates to BackupEngine (which already sends notifications gated by
the profile's notify_on_success/notify_on_failure and applies retention —
so there is exactly ONE notification layer), and normalises the result,
including the real complete/warning/fail status the engine records but did
not return. BackupRunner self-loads BackupEngine so callers require one file.

Routed through BackupRunner:
- plg_system  runPreActionBackup() (preaction) + web-cron handler (webcron)
- plg_content triggerAutoBackup() (pre-install)
- plg_task    scheduled task run
- plg_console RunCommand (cli)

The dashboard AJAX/stepped "Backup Now" flow is unchanged (it is the
interactive equivalent). The installer progress popup is deferred — the
installer request cannot drive the AJAX modal (TODO(#196) noted in code).

php -l clean on all changed files.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 17:28:59 -05:00

110 lines
3.7 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
*
* Single synchronous entry point for running a backup.
*
* Every non-interactive trigger — the web-cron handler, the pre-install /
* pre-update / pre-uninstall hooks, the scheduled task plugin and the CLI
* command — runs through here, so the final status, notification gating and
* retention are applied identically regardless of what started the backup.
* (The dashboard's AJAX "Backup Now" is the interactive equivalent, driven by
* SteppedBackupEngine.)
*
* BackupEngine::run() already sends notifications (gated by the profile's
* notify_on_success / notify_on_failure) and applies retention, so this class
* DELEGATES rather than reimplementing that logic — there is exactly one
* notification layer. Its value is being the documented seam where origin-aware
* behaviour can live and normalising the result, including the real
* complete / warning / fail status that the engine records but does not return.
*/
namespace Joomla\Component\MokoSuiteBackup\Administrator\Service;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine;
final class BackupRunner
{
/**
* Run a full backup synchronously and return a normalised result.
*
* @param int $profileId Profile to back up.
* @param string $description Human-readable description stored on the record.
* @param string $origin Trigger origin (webcron|preaction|scheduled|cli|backend…).
*
* @return array{success:bool,status:string,message:string,record_id:int,warnings:array}
*/
public function run(int $profileId, string $description, string $origin = 'backend'): array
{
// TODO(#196): an interactive progress popup for installer-triggered runs
// (onExtensionBeforeUpdate) is a separate follow-up — the installer
// request cannot drive the AJAX/stepped progress modal.
try {
if (!class_exists(BackupEngine::class)) {
require_once __DIR__ . '/../Engine/BackupEngine.php';
}
$result = (new BackupEngine())->run($profileId, $description, $origin);
} catch (\Throwable $e) {
error_log('MokoSuiteBackup: backup run failed (' . $origin . '): ' . $e->getMessage());
return [
'success' => false,
'status' => 'fail',
'message' => 'Backup failed: ' . $e->getMessage(),
'record_id' => 0,
'warnings' => [],
];
}
$success = (bool) ($result['success'] ?? false);
$recordId = (int) ($result['record_id'] ?? 0);
return [
'success' => $success,
'status' => $this->resolveStatus($success, $recordId),
'message' => (string) ($result['message'] ?? ''),
'record_id' => $recordId,
'warnings' => $result['warnings'] ?? [],
];
}
/**
* Read the status BackupEngine wrote to the record (complete/warning/fail)
* but does not return, so callers can distinguish a warning (archive created,
* remote upload failed) from a clean success.
*/
private function resolveStatus(bool $success, int $recordId): string
{
if (!$success) {
return 'fail';
}
if ($recordId <= 0) {
return 'complete';
}
try {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('status'))
->from($db->quoteName('#__mokosuitebackup_records'))
->where($db->quoteName('id') . ' = ' . (int) $recordId);
$db->setQuery($query);
$status = (string) $db->loadResult();
return $status !== '' ? $status : 'complete';
} catch (\Throwable $e) {
return 'complete';
}
}
}