diff --git a/CHANGELOG.md b/CHANGELOG.md index 57002893..f41d813c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,7 @@ ## [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. (#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) +- **Full-screen backup screen** (`view=runbackup`) for both pre-update and manual backups, modelled on Akeeba's Backup-on-Update — replaces the earlier popup-modal approach. Clicking Joomla's **Install the update** now redirects (server-side, Akeeba-style) to a dedicated full-page backup screen that runs the stepped backup with a real progress bar and then **automatically continues the update** via a validated `returnurl` (flagged `is_backed_up=1` so the backup isn't repeated). Crucially, **no backup runs synchronously inside the update request** — which is what white-screened large sites. The dashboard **Backup Now** now opens the same full-screen screen instead of an inline modal. (#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 diff --git a/source/packages/com_mokosuitebackup/config.xml b/source/packages/com_mokosuitebackup/config.xml index 94650c5b..84db7fca 100644 --- a/source/packages/com_mokosuitebackup/config.xml +++ b/source/packages/com_mokosuitebackup/config.xml @@ -151,18 +151,6 @@ - - - - + * @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\View\Runbackup; + +defined('_JEXEC') or die; + +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; +use Joomla\CMS\Toolbar\ToolbarHelper; + +/** + * Full-screen "run a backup" progress view. + * + * Auto-starts the stepped backup (ajax.init → loop ajax.step) and shows a + * full-page progress screen. Used both by the dashboard "Backup Now" action + * and — via the system plugin's pre-update redirect — as the interstitial + * between clicking Joomla's Update and the update actually running. When a + * `returnurl` is supplied the page redirects there once the backup completes + * (e.g. back to `com_joomlaupdate&task=update.install&is_backed_up=1`). + */ +class HtmlView extends BaseHtmlView +{ + public int $profileId = 1; + + public string $profileTitle = ''; + + public string $description = ''; + + /** Raw (possibly base64) return URL from the request; validated in the layout. */ + public string $returnUrl = ''; + + public bool $autostart = true; + + public function display($tpl = null): void + { + $input = Factory::getApplication()->getInput(); + + $this->profileId = (int) $input->getInt('profile_id', $input->getInt('profileid', 1)); + $this->description = $input->getString('description', ''); + $this->returnUrl = $input->getRaw('returnurl', ''); + $this->autostart = (bool) $input->getInt('autostart', 1); + + if ($this->profileId <= 0) { + $this->profileId = 1; + } + + $this->profileTitle = $this->loadProfileTitle($this->profileId); + + $this->addToolbar(); + + parent::display($tpl); + } + + /** + * Look up the target profile's title for display (best-effort). + */ + private function loadProfileTitle(int $profileId): string + { + try { + $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class); + $query = $db->getQuery(true) + ->select($db->quoteName('title')) + ->from($db->quoteName('#__mokosuitebackup_profiles')) + ->where($db->quoteName('id') . ' = ' . (int) $profileId); + $db->setQuery($query); + + return (string) ($db->loadResult() ?? ''); + } catch (\Throwable $e) { + return ''; + } + } + + protected function addToolbar(): void + { + ToolbarHelper::title( + Text::_('COM_MOKOJOOMBACKUP_SHORT') . ': ' . Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE'), + 'archive' + ); + } +} diff --git a/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php b/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php index 565acd9e..e861acdc 100644 --- a/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php +++ b/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php @@ -17,6 +17,9 @@ use Joomla\CMS\Session\Session; $ajaxToken = Session::getFormToken(); $ajaxUrl = Route::_('index.php?option=com_mokosuitebackup&format=json', false); + +$runbackupUrl = Route::_('index.php?option=com_mokosuitebackup&view=runbackup&autostart=1', false); +$liveSite = trim((string) \Joomla\CMS\Factory::getApplication()->get('live_site', '')); ?> defaultDirWarning) : ?> + + + +
@@ -244,7 +257,7 @@ document.querySelectorAll('.mb-tile').forEach(function(tile) { - @@ -302,123 +315,18 @@ document.querySelectorAll('.mb-tile').forEach(function(tile) {
- - - diff --git a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php new file mode 100644 index 00000000..d4cceddd --- /dev/null +++ b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php @@ -0,0 +1,240 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + * + * Full-screen backup progress screen. Reuses the stepped-backup AJAX + * (ajax.init → loop ajax.step). On completion, redirects to a validated + * return URL (pre-update flow) or shows a completion panel (Backup Now). + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\Language\Text; +use Joomla\CMS\Router\Route; +use Joomla\CMS\Session\Session; +use Joomla\CMS\Uri\Uri; + +$ajaxToken = Session::getFormToken(); +$ajaxUrl = Route::_('index.php?option=com_mokosuitebackup&format=json', false); + +/* Validate the return URL to prevent an open redirect: accept only a relative + URL or one whose host matches this site. Falls back to '' (no redirect). */ +$safeReturnUrl = ''; + +if ($this->returnUrl !== '') { + $decoded = base64_decode($this->returnUrl, true); + $raw = ($decoded !== false && $decoded !== '') ? $decoded : $this->returnUrl; + + try { + $rootHost = Uri::getInstance(Uri::root())->getHost(); + $target = new Uri($raw); + + if ($target->getHost() === '' || strcasecmp($target->getHost(), $rootHost) === 0) { + $safeReturnUrl = $raw; + } + } catch (\Throwable $e) { + $safeReturnUrl = ''; + } +} + +$dashboardUrl = Route::_('index.php?option=com_mokosuitebackup&view=dashboard', false); + +$config = [ + 'ajaxUrl' => $ajaxUrl, + 'token' => $ajaxToken, + 'profileId' => $this->profileId, + 'description' => $this->description, + 'returnUrl' => $safeReturnUrl, + 'dashboardUrl' => $dashboardUrl, + 'autostart' => $this->autostart, + 'labels' => [ + 'starting' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), + 'running' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_RUNNING'), + 'complete' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_COMPLETE'), + 'continuing' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUING'), + 'failed' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED'), + 'retry' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY'), + 'continue' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY'), + 'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'), + 'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'), + ], +]; +?> +
+
+
+

+ + +

+

+ profileTitle !== '' + ? Text::sprintf('COM_MOKOJOOMBACKUP_RUNBACKUP_PROFILE', $this->profileTitle) + : Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), + ENT_QUOTES, + 'UTF-8' + ); ?> +

+ +
+
0%
+
+ +
+
+ +
+ + + +
+
+
+
+ + diff --git a/source/packages/plg_system_mokosuitebackup/media/js/update-backup.js b/source/packages/plg_system_mokosuitebackup/media/js/update-backup.js deleted file mode 100644 index 5cdc53d2..00000000 --- a/source/packages/plg_system_mokosuitebackup/media/js/update-backup.js +++ /dev/null @@ -1,261 +0,0 @@ -/** - * MokoSuiteBackup — pre-update backup notice + live-progress modal. - * - * Injected by plg_system_mokosuitebackup on the Joomla core-update - * (com_joomlaupdate) and extensions-update (com_installer&view=update) pages. - * - * Why a client-side modal: the server-side onExtensionBeforeUpdate backup runs - * synchronously mid-request and cannot drive a browser modal. This offers an - * on-demand "Back up now" button that runs the same stepped backup the - * dashboard uses (ajax.init then a loop of ajax.step) and shows live progress. - * On success it pings ajax.preupdateAck so the imminent server-side pre-update - * backup is skipped (no duplicate backup) when the admin clicks Update. - * - * Config comes from Joomla.getOptions('plg_system_mokosuitebackup.preupdate'). - */ -(function () { - 'use strict'; - - var cfg = (window.Joomla && Joomla.getOptions) - ? Joomla.getOptions('plg_system_mokosuitebackup.preupdate', null) - : null; - - if (!cfg || !cfg.ajaxUrl || !cfg.token) { - return; - } - - 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; - - if (!host || document.getElementById('msb-preupdate-notice')) { - return; - } - - var bar = document.createElement('div'); - bar.id = 'msb-preupdate-notice'; - bar.setAttribute('role', 'status'); - bar.style.cssText = 'display:flex;align-items:center;gap:1rem;flex-wrap:wrap;' - + 'margin:0 0 1rem 0;padding:.75rem 1rem;border:1px solid #f0c36d;' - + 'background:#fcf8e3;border-radius:.35rem;color:#8a6d3b;font-size:.95rem;'; - - var text = document.createElement('span'); - text.style.cssText = 'flex:1 1 auto;'; - text.textContent = L.notice || 'A full-site backup is recommended before you update.'; - - var btn = document.createElement('button'); - btn.type = 'button'; - btn.id = 'msb-preupdate-run'; - btn.className = 'btn btn-warning btn-sm'; - btn.textContent = L.backupNow || 'Back up now'; - btn.addEventListener('click', runBackup); - - var dismiss = document.createElement('button'); - dismiss.type = 'button'; - dismiss.className = 'btn btn-link btn-sm'; - dismiss.style.cssText = 'color:#8a6d3b;'; - dismiss.textContent = L.dismiss || 'Dismiss'; - dismiss.addEventListener('click', function () { bar.remove(); }); - - bar.appendChild(text); - bar.appendChild(btn); - bar.appendChild(dismiss); - host.insertBefore(bar, host.firstChild); - } - - /* ── The live-progress overlay (dependency-free, mirrors the dashboard) ── */ - - function buildOverlay() { - var overlay = document.createElement('div'); - overlay.id = 'msb-preupdate-overlay'; - overlay.style.cssText = 'position:fixed;inset:0;z-index:10000;display:flex;' - + 'align-items:center;justify-content:center;background:rgba(0,0,0,.5);'; - - var box = document.createElement('div'); - box.style.cssText = 'width:min(480px,92vw);background:#fff;border-radius:.5rem;' - + 'padding:1.5rem;box-shadow:0 10px 40px rgba(0,0,0,.3);'; - - var title = document.createElement('h3'); - title.id = 'msb-pu-title'; - title.style.cssText = 'margin:0 0 1rem 0;font-size:1.15rem;'; - title.textContent = L.backingUp || 'Backing up…'; - - var track = document.createElement('div'); - track.style.cssText = 'height:1.25rem;background:#e9ecef;border-radius:.35rem;overflow:hidden;'; - - var barfill = document.createElement('div'); - barfill.id = 'msb-pu-bar'; - barfill.style.cssText = 'height:100%;width:0;background:#198754;transition:width .3s ease;'; - track.appendChild(barfill); - - var status = document.createElement('div'); - status.id = 'msb-pu-status'; - status.style.cssText = 'margin-top:.75rem;font-size:.9rem;color:#555;'; - status.textContent = L.starting || 'Starting backup…'; - - box.appendChild(title); - box.appendChild(track); - box.appendChild(status); - overlay.appendChild(box); - document.body.appendChild(overlay); - - return { overlay: overlay, title: title, bar: barfill, status: status }; - } - - function setProgress(ui, progress, message) { - var pct = Math.max(0, Math.min(100, parseInt(progress, 10) || 0)); - ui.bar.style.width = pct + '%'; - - if (message) { - ui.status.textContent = message; - } - } - - function post(params) { - var body = new URLSearchParams(); - body.append(cfg.token, '1'); - - Object.keys(params).forEach(function (k) { - body.append(k, params[k]); - }); - - return fetch(cfg.ajaxUrl, { - method: 'POST', - body: body, - headers: { 'X-Requested-With': 'XMLHttpRequest' } - }).then(function (r) { return r.json(); }); - } - - async function runBackup() { - if (running) { - return; - } - - running = true; - - var runBtn = document.getElementById('msb-preupdate-run'); - - if (runBtn) { - runBtn.disabled = true; - } - - var ui = buildOverlay(); - var guard = function (e) { e.preventDefault(); e.returnValue = ''; }; - window.addEventListener('beforeunload', guard); - - try { - var init = await post({ task: 'ajax.init', profile_id: cfg.profileId }); - - if (!init || init.error || !init.session_id) { - throw new Error((init && init.message) || 'Could not start backup'); - } - - setProgress(ui, init.progress, init.message); - - var done = false; - - while (!done) { - var step = await post({ task: 'ajax.step', session_id: init.session_id }); - - if (!step || step.error) { - throw new Error((step && step.message) || 'Backup step failed'); - } - - setProgress(ui, step.progress, step.message); - done = step.done || false; - } - - /* Tell the server the pre-update backup is satisfied so the - 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, ''); - - var notice = document.getElementById('msb-preupdate-notice'); - - if (notice) { - notice.style.background = '#d1e7dd'; - notice.style.borderColor = '#badbcc'; - notice.style.color = '#0f5132'; - } - - setTimeout(function () { ui.overlay.remove(); }, 1500); - - return true; - } catch (err) { - ui.title.textContent = (L.failed || 'Backup failed') + ': ' + err.message; - ui.bar.style.background = '#dc3545'; - - if (runBtn) { - runBtn.disabled = false; - } - - return false; - } finally { - running = false; - window.removeEventListener('beforeunload', guard); - } - } -})(); diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 93ebdd58..de1ff531 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -29,10 +29,6 @@ language/en-GB/plg_system_mokosuitebackup.sys.ini - - js - -
diff --git a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php index b2399603..41ae8765 100644 --- a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php +++ b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php @@ -13,11 +13,8 @@ namespace Joomla\Plugin\System\MokoSuiteBackup\Extension; defined('_JEXEC') or die; use Joomla\CMS\Component\ComponentHelper; -use Joomla\CMS\Document\HtmlDocument; use Joomla\CMS\Factory; -use Joomla\CMS\Language\Text; use Joomla\CMS\Plugin\CMSPlugin; -use Joomla\CMS\Router\Route; use Joomla\CMS\Session\Session; use Joomla\CMS\Uri\Uri; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RetentionManager; @@ -34,7 +31,6 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface return [ 'onAfterInitialise' => 'onAfterInitialise', 'onAfterRoute' => 'onAfterRoute', - 'onBeforeCompileHead' => 'onBeforeCompileHead', 'onExtensionBeforeUpdate' => 'onExtensionBeforeUpdate', 'onExtensionBeforeUninstall' => 'onExtensionBeforeUninstall', ]; @@ -127,6 +123,10 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface return; } + // Pre-update: send Joomla core-update installs through the full-screen + // backup page BEFORE the update runs (may not return from here). + $this->maybeRedirectForUpdate(); + if (!(int) $this->params->get('auto_cleanup', 1)) { return; } @@ -146,74 +146,79 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface } /** - * Inject the pre-update backup notice + live-progress modal on the admin - * update pages (com_joomlaupdate and com_installer's update view). + * Send a Joomla core-update install through the full-screen backup page + * BEFORE the update runs (Akeeba-style), so no backup runs synchronously + * inside the update request (which is what white-screened large sites). * - * The server-side onExtensionBeforeUpdate backup runs synchronously and - * cannot drive a browser modal, so this offers an on-demand "Back up now" - * button that runs the same stepped backup as the dashboard and shows live - * progress. On success the client pings ajax.preupdateAck, which arms the - * same throttle key runPreActionBackup() checks — so the subsequent server - * update does not run a duplicate backup. + * Flow: user clicks "Install the update" (com_joomlaupdate&task=update.install) + * → we redirect to com_mokosuitebackup&view=runbackup (full screen) with a + * returnurl back to update.install&is_backed_up=1 → the backup runs on its + * own page with real progress → on completion the browser returns to + * update.install, we arm the throttle so onExtensionBeforeUpdate doesn't + * duplicate the backup, and the update proceeds. */ - public function onBeforeCompileHead(Event $event): void + private function maybeRedirectForUpdate(): void { - $app = $this->getApplication(); - - if (!$app->isClient('administrator')) { - return; - } - - $input = $app->getInput(); - $option = $input->getCmd('option', ''); - $view = $input->getCmd('view', ''); - - $onUpdatePage = $option === 'com_joomlaupdate' - || ($option === 'com_installer' && $view === 'update'); - - if (!$onUpdatePage) { - return; - } - $params = ComponentHelper::getParams('com_mokosuitebackup'); - // Respect the notice toggle and the pre-update backup feature flag. - if (!(int) $params->get('show_update_notice', 1) || !(int) $params->get('backup_before_update', 0)) { + if (!(int) $params->get('backup_before_update', 0)) { return; } - // Already backed up this session (within the throttle window)? Stay quiet. - $lastRun = (int) Factory::getSession()->get('mokosuitebackup.preaction_backup_before_update', 0); + $app = $this->getApplication(); + $input = $app->getInput(); + + if ($input->getCmd('option', '') !== 'com_joomlaupdate' + || $input->getCmd('task', '') !== 'update.install') { + return; + } + + $session = Factory::getSession(); + + // Returned from the backup screen: arm the throttle so the synchronous + // onExtensionBeforeUpdate backup doesn't run again, then let it proceed. + if ((int) $input->getInt('is_backed_up', 0) === 1) { + $session->set('mokosuitebackup.preaction_backup_before_update', time()); + + return; + } + + // Backed up recently already — let the update proceed. + $lastRun = (int) $session->get('mokosuitebackup.preaction_backup_before_update', 0); if ($lastRun > 0 && (time() - $lastRun) < 600) { return; } - $doc = $app->getDocument(); + // Super Users only. + $user = $app->getIdentity(); - if (!$doc instanceof HtmlDocument) { + if (!$user || $user->guest || !$user->authorise('core.admin')) { return; } - $doc->getWebAssetManager()->useScript('core'); + $token = Session::getFormToken(); + $profileId = (int) $params->get('default_profile', 1); - $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), - 'autoIntercept' => (bool) (int) $params->get('preupdate_auto_intercept', 0), - 'labels' => [ - 'notice' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_UPDATE_NOTICE'), - 'backupNow' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_NOW'), - 'dismiss' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_DISMISS'), - 'backingUp' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKING_UP'), - 'starting' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING'), - 'complete' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE'), - 'failed' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED'), - ], - ]); + // Where to send the browser after the backup: back to the update install. + $returnUri = new Uri(Uri::base() . 'index.php'); + $returnUri->setVar('option', 'com_joomlaupdate'); + $returnUri->setVar('task', 'update.install'); + $returnUri->setVar('is_backed_up', '1'); + $returnUri->setVar($token, '1'); - $doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/update-backup.js', [], ['defer' => true]); + // The full-screen backup page (chromeless via tmpl=component). + $redirect = new Uri(Uri::base() . 'index.php'); + $redirect->setVar('option', 'com_mokosuitebackup'); + $redirect->setVar('view', 'runbackup'); + $redirect->setVar('tmpl', 'component'); + $redirect->setVar('autostart', '1'); + $redirect->setVar('profile_id', (string) $profileId); + $redirect->setVar('description', 'Pre-update backup'); + $redirect->setVar('returnurl', base64_encode($returnUri->toString())); + $redirect->setVar($token, '1'); + + $app->redirect($redirect->toString()); } /**