feat: pre-update modal phase 2 — auto-run backup on Update click (#196) #233
+2
-1
@@ -3,7 +3,8 @@
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Pre-update backup **notice + live-progress modal** on the admin update pages (Joomla Update and Extensions → Update). Because the server-side `onExtensionBeforeUpdate` backup runs synchronously and can't drive a browser modal, the system plugin now injects a "back up before you update" notice with a **Back up now** button that runs the same stepped backup as the dashboard and shows a live progress bar. On success it pings a new `ajax.preupdateAck` endpoint, which arms the same throttle the server-side hook checks — so clicking Joomla's Update afterwards won't run a duplicate backup. Gated by the existing `show_update_notice` + `backup_before_update` params. (Auto-intercepting the Update button is the planned phase-2 follow-up — see #196.) (#196)
|
||||
- Pre-update backup **notice + live-progress modal** on the admin update pages (Joomla Update and Extensions → Update). Because the server-side `onExtensionBeforeUpdate` backup runs synchronously and can't drive a browser modal, the system plugin now injects a "back up before you update" notice with a **Back up now** button that runs the same stepped backup as the dashboard and shows a live progress bar. On success it pings a new `ajax.preupdateAck` endpoint, which arms the same throttle the server-side hook checks — so clicking Joomla's Update afterwards won't run a duplicate backup. Gated by the existing `show_update_notice` + `backup_before_update` params. (#196)
|
||||
- Pre-update modal **phase 2 — "Auto-run on Update click"** (new opt-in param `preupdate_auto_intercept`, off by default). When enabled, clicking the toolbar **Update** button on the Joomla Update / Extensions-update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. Implemented by wrapping `Joomla.submitbutton` for the `update.update` / `update.install` tasks only (Find Updates / Clear Cache are untouched); the manual "Back up now" notice remains as a fallback for any flow that doesn't route through `submitbutton`. (#196)
|
||||
- Retention now prunes **remote** copies too: when a backup is pruned by age/count, its archive is deleted from every enabled remote destination (SFTP / FTP / S3 / Google Drive), not just the local copy. Each uploader gained an idempotent `delete()` method (already-absent file = success), and removal is best-effort — a failing destination is logged but never blocks local pruning. The shared standalone `restore.php` is intentionally left in place (every backup overwrites it, so newer backups still depend on it). (#229)
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -151,6 +151,18 @@
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field
|
||||
name="preupdate_auto_intercept"
|
||||
type="radio"
|
||||
label="COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT"
|
||||
description="COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT_DESC"
|
||||
default="0"
|
||||
class="btn-group"
|
||||
showon="backup_before_update:1"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field
|
||||
name="backup_before_uninstall"
|
||||
type="radio"
|
||||
|
||||
@@ -331,6 +331,8 @@ COM_MOKOJOOMBACKUP_CONFIG_SHOW_UPDATE_NOTICE_DESC="Display the update site confi
|
||||
COM_MOKOJOOMBACKUP_CONFIG_PREACTION="Pre-action Backups"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE="Backup Before Extension Update"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE_DESC="Automatically run a full backup before any extension is updated. Uses the default profile. Throttled to once per 10 minutes to prevent duplicate backups during batch updates."
|
||||
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT="Auto-run on Update click"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT_DESC="When enabled, clicking the Update button on the Joomla Update or Extensions update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. When disabled, a notice with a manual 'Back up now' button is shown instead."
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes."
|
||||
|
||||
|
||||
@@ -53,6 +53,8 @@ COM_MOKOJOOMBACKUP_CONFIG_SHOW_UPDATE_NOTICE_DESC="Display the update site confi
|
||||
COM_MOKOJOOMBACKUP_CONFIG_PREACTION="Pre-action Backups"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE="Backup Before Extension Update"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE_DESC="Automatically run a full backup before any extension is updated. Uses the default profile. Throttled to once per 10 minutes to prevent duplicate backups during batch updates."
|
||||
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT="Auto-run on Update click"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT_DESC="When enabled, clicking the Update button on the Joomla Update or Extensions update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. When disabled, a notice with a manual 'Back up now' button is shown instead."
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes."
|
||||
|
||||
|
||||
@@ -26,11 +26,65 @@
|
||||
|
||||
var L = cfg.labels || {};
|
||||
var running = false;
|
||||
var backedUp = false;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
injectNotice();
|
||||
});
|
||||
|
||||
if (cfg.autoIntercept) {
|
||||
setupInterception();
|
||||
}
|
||||
|
||||
/* ── Phase 2: auto-run on the Update click ──────────────────────────────
|
||||
* Wrap window.Joomla.submitbutton so clicking the toolbar Update button
|
||||
* first runs the backup modal, then continues the original update once the
|
||||
* backup succeeds. submitbutton is synchronous, so we can't pause it — we
|
||||
* swallow the update task, run the async backup, and re-issue the original
|
||||
* task ourselves on success. The notice's "Back up now" button remains a
|
||||
* fallback for any flow that doesn't route through submitbutton. */
|
||||
function setupInterception() {
|
||||
if (!wrapSubmit()) {
|
||||
document.addEventListener('DOMContentLoaded', wrapSubmit);
|
||||
}
|
||||
}
|
||||
|
||||
function wrapSubmit() {
|
||||
var J = window.Joomla;
|
||||
|
||||
if (!J || typeof J.submitbutton !== 'function' || J.submitbutton.__msbWrapped) {
|
||||
return !!(J && J.submitbutton && J.submitbutton.__msbWrapped);
|
||||
}
|
||||
|
||||
var original = J.submitbutton;
|
||||
|
||||
var wrapped = function (task) {
|
||||
if (!backedUp && !running && isUpdateTask(task)) {
|
||||
var args = arguments;
|
||||
runBackup().then(function (ok) {
|
||||
if (ok) {
|
||||
original.apply(window.Joomla, args);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return original.apply(window.Joomla, arguments);
|
||||
};
|
||||
|
||||
wrapped.__msbWrapped = true;
|
||||
J.submitbutton = wrapped;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Only the actual "apply the update" tasks — never Find Updates / Clear
|
||||
* Cache (update.find / update.purge) which also start with "update". */
|
||||
function isUpdateTask(task) {
|
||||
return task === 'update.update' || task === 'update.install';
|
||||
}
|
||||
|
||||
function injectNotice() {
|
||||
var host = document.querySelector('main') || document.querySelector('#content') || document.body;
|
||||
|
||||
@@ -175,6 +229,7 @@
|
||||
synchronous onExtensionBeforeUpdate backup is skipped. */
|
||||
try { await post({ task: 'ajax.preupdateAck' }); } catch (ignore) {}
|
||||
|
||||
backedUp = true;
|
||||
ui.title.textContent = L.complete || 'Backup complete — safe to update.';
|
||||
setProgress(ui, 100, '');
|
||||
|
||||
@@ -187,6 +242,8 @@
|
||||
}
|
||||
|
||||
setTimeout(function () { ui.overlay.remove(); }, 1500);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
ui.title.textContent = (L.failed || 'Backup failed') + ': ' + err.message;
|
||||
ui.bar.style.background = '#dc3545';
|
||||
@@ -194,6 +251,8 @@
|
||||
if (runBtn) {
|
||||
runBtn.disabled = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
running = false;
|
||||
window.removeEventListener('beforeunload', guard);
|
||||
|
||||
@@ -198,9 +198,10 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
|
||||
$doc->getWebAssetManager()->useScript('core');
|
||||
|
||||
$doc->addScriptOptions('plg_system_mokosuitebackup.preupdate', [
|
||||
'ajaxUrl' => Route::_('index.php?option=com_mokosuitebackup&format=json', false),
|
||||
'token' => Session::getFormToken(),
|
||||
'profileId' => (int) $params->get('default_profile', 1),
|
||||
'ajaxUrl' => Route::_('index.php?option=com_mokosuitebackup&format=json', false),
|
||||
'token' => Session::getFormToken(),
|
||||
'profileId' => (int) $params->get('default_profile', 1),
|
||||
'autoIntercept' => (bool) (int) $params->get('preupdate_auto_intercept', 0),
|
||||
'labels' => [
|
||||
'notice' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_UPDATE_NOTICE'),
|
||||
'backupNow' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_NOW'),
|
||||
|
||||
Reference in New Issue
Block a user