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
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Joomla\Plugin\Console\MokoSuiteBackup\Command;
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine;
|
||||
use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner;
|
||||
use Joomla\Console\Command\AbstractCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@@ -40,20 +40,19 @@ class RunCommand extends AbstractCommand
|
||||
$io->title('MokoSuiteBackup — Run Backup');
|
||||
$io->text('Profile ID: ' . $profileId);
|
||||
|
||||
$engineFile = JPATH_ADMINISTRATOR . '/components/com_mokosuitebackup/src/Engine/BackupEngine.php';
|
||||
$runnerFile = JPATH_ADMINISTRATOR . '/components/com_mokosuitebackup/src/Service/BackupRunner.php';
|
||||
|
||||
if (!file_exists($engineFile)) {
|
||||
if (!file_exists($runnerFile)) {
|
||||
$io->error('MokoSuiteBackup component not installed.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!class_exists(BackupEngine::class)) {
|
||||
require_once $engineFile;
|
||||
if (!class_exists(BackupRunner::class)) {
|
||||
require_once $runnerFile;
|
||||
}
|
||||
|
||||
$engine = new BackupEngine();
|
||||
$result = $engine->run($profileId, $desc ?: 'CLI backup', 'cli');
|
||||
$result = (new BackupRunner())->run($profileId, $desc ?: 'CLI backup', 'cli');
|
||||
|
||||
if ($result['success']) {
|
||||
$io->success($result['message']);
|
||||
|
||||
+6
-7
@@ -14,7 +14,7 @@ defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine;
|
||||
use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner;
|
||||
use Joomla\Event\Event;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
@@ -61,19 +61,18 @@ final class MokoSuiteBackupContent extends CMSPlugin implements SubscriberInterf
|
||||
|
||||
$session->set('mokosuitebackup.content_last_autobackup', time());
|
||||
|
||||
$engineFile = JPATH_ADMINISTRATOR . '/components/com_mokosuitebackup/src/Engine/BackupEngine.php';
|
||||
$runnerFile = JPATH_ADMINISTRATOR . '/components/com_mokosuitebackup/src/Service/BackupRunner.php';
|
||||
|
||||
if (!file_exists($engineFile)) {
|
||||
if (!file_exists($runnerFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!class_exists(BackupEngine::class)) {
|
||||
require_once $engineFile;
|
||||
if (!class_exists(BackupRunner::class)) {
|
||||
require_once $runnerFile;
|
||||
}
|
||||
|
||||
try {
|
||||
$engine = new BackupEngine();
|
||||
$engine->run($profileId, $description, 'backend');
|
||||
(new BackupRunner())->run($profileId, $description, 'backend');
|
||||
} catch (\Throwable $e) {
|
||||
// Non-fatal — log and continue with the install/update
|
||||
Factory::getApplication()->enqueueMessage(
|
||||
|
||||
@@ -15,7 +15,7 @@ defined('_JEXEC') or die;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine;
|
||||
use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner;
|
||||
use Joomla\Event\Event;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
@@ -91,8 +91,7 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
|
||||
@ini_set('memory_limit', '512M');
|
||||
|
||||
try {
|
||||
$engine = new BackupEngine();
|
||||
$result = $engine->run($profileId, 'Web cron backup', 'webcron');
|
||||
$result = (new BackupRunner())->run($profileId, 'Web cron backup', 'webcron');
|
||||
|
||||
$this->sendJsonResponse(
|
||||
$result['success'],
|
||||
@@ -390,8 +389,7 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
|
||||
$profileId = (int) $params->get('default_profile', 1);
|
||||
|
||||
try {
|
||||
$engine = new BackupEngine();
|
||||
$result = $engine->run($profileId, $description, 'preaction');
|
||||
$result = (new BackupRunner())->run($profileId, $description, 'preaction');
|
||||
|
||||
if (!$result['success']) {
|
||||
Factory::getApplication()->enqueueMessage(
|
||||
|
||||
@@ -72,7 +72,7 @@ final class MokoSuiteBackupTask extends CMSPlugin implements SubscriberInterface
|
||||
$profileId = (int) ($params->profile_id ?? 1);
|
||||
|
||||
// Load the backup engine from the component
|
||||
$engineFile = JPATH_ADMINISTRATOR . '/components/com_mokosuitebackup/src/Engine/BackupEngine.php';
|
||||
$engineFile = JPATH_ADMINISTRATOR . '/components/com_mokosuitebackup/src/Service/BackupRunner.php';
|
||||
|
||||
if (!file_exists($engineFile)) {
|
||||
$this->logTask('MokoSuiteBackup component not installed — cannot run backup.');
|
||||
@@ -81,11 +81,11 @@ final class MokoSuiteBackupTask extends CMSPlugin implements SubscriberInterface
|
||||
}
|
||||
|
||||
// The autoloader should handle this via namespace, but ensure class is available
|
||||
if (!class_exists('\\Joomla\\Component\\MokoSuiteBackup\\Administrator\\Engine\\BackupEngine')) {
|
||||
if (!class_exists('\\Joomla\\Component\\MokoSuiteBackup\\Administrator\\Service\\BackupRunner')) {
|
||||
require_once $engineFile;
|
||||
}
|
||||
|
||||
$engine = new \Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine();
|
||||
$engine = new \Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner();
|
||||
$result = $engine->run($profileId, 'Scheduled task backup', 'scheduled');
|
||||
|
||||
if ($result['success']) {
|
||||
|
||||
Reference in New Issue
Block a user