diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml
index cc754b16..fd83b23a 100644
--- a/.mokogitea/workflows/issue-branch.yml
+++ b/.mokogitea/workflows/issue-branch.yml
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Automation
-# VERSION: 02.58.17
+# VERSION: 02.58.20
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94209868..4fec5ae0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,8 @@
## [Unreleased]
### Added
+- Full-screen backup now also fronts **extension updates and uninstalls** (Extensions → Update / Manage), not just core Joomla updates. Because `com_installer`'s `update.update` / `manage.remove` are POST actions (CSRF token + a checked `cid[]` list) that a server-side redirect can't cleanly resume, this is done client-side: the toolbar Update/Uninstall click is intercepted, the selection is captured, the full-screen backup runs, and on return the original selection is restored and the real POST form is re-submitted. Gated by `backup_before_update` / `backup_before_uninstall`, super-user only, and skipped if a backup already ran this session.
+- Manual **"Backup Now"** completion now offers a **View backup record** button (links straight to the record that was just created). It appears only for manual backups — the pre-update / pre-uninstall flow hands control back to Joomla instead.
- **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)
@@ -14,6 +16,7 @@
### Fixed
- Package installer is now **honest about success**: the postflight no longer prints "installed successfully" / next-steps when the install actually failed or only partially completed. Joomla logs a failed child extension but still runs the package postflight, so the postflight now (a) verifies every bundled child declared in the manifest actually registered in `#__extensions` (matched by element + type, and folder/group for plugins) and (b) verifies the component's schema tables — derived dynamically from the installed `install.mysql.sql` — actually exist. If anything is missing it enqueues an error listing what's missing and stops before the success message. Fail-open: any manifest/DB/IO glitch is treated as "nothing missing" so a transient error never turns a good install into a false failure. Unconditional housekeeping (e.g. download-key restore) still runs regardless.
+- Pre-update full-screen backup screen now actually triggers on **Joomla 6** (and 4/5). The redirect matched only the legacy `update.install` task, which Joomla 4/5/6 don't use — they server-side-redirect to the **updating page `view=update`**, which then extracts the downloaded package from JavaScript. The plugin now intercepts the `view=update` page **load** (the last point before any files change) and returns the browser there flagged `is_backed_up=1` so the extraction proceeds after the backup. (`update.finalise` is intentionally not intercepted — by then the files are already extracted.)
- Pre-update/uninstall backup no longer **white-screens** the update on large sites. The synchronous backup that runs inside the extension update/uninstall request now raises `max_execution_time`/`memory_limit` (and `ignore_user_abort`) like the web-cron path, so it can't exhaust the request's default limits mid-backup. (Core Joomla updates additionally get a full-screen backup screen — see below.)
- Archive names for **CLI/console-triggered backups** no longer come out as `joomla.invalid_…`. The `[HOST]` placeholder took `$_SERVER['HTTP_HOST']` verbatim, but Joomla's console fills that with the reserved sentinel host `joomla.invalid`; the resolver now treats that (like empty/`localhost`) as unusable and falls back to the configured `live_site` host, then the system hostname. (Set the site's *live_site* to get the exact domain in CLI-built names.)
- Standalone restore script generation no longer aborts backups with `str_replace() expects at least 3 arguments, 2 given`. `MokoRestore::generateStandaloneScript()` had a `str_replace()` call (the "Backup Archive" pre-check rewrite) that was missing its `$php` subject argument, so **every** standalone-mode backup fatally errored while "Generating standalone restore.php…" — the archive still finalized and uploaded, but no `restore.php` was ever produced (the true root cause behind #226). (#226)
diff --git a/SECURITY.md b/SECURITY.md
index 7e1087cb..d46214dc 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md
-VERSION: 02.58.17
+VERSION: 02.58.20
BRIEF: Security vulnerability reporting and handling policy
-->
diff --git a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini
index b6793949..d6cb00b8 100644
--- a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini
+++ b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini
@@ -347,6 +347,7 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED="Backup failed"
COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY="Retry backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
+COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
diff --git a/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini
index 7789c61e..d344d799 100644
--- a/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini
+++ b/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini
@@ -69,6 +69,7 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED="Backup failed"
COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY="Retry backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
+COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml
index 233d8464..546dac09 100644
--- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml
+++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml
@@ -17,7 +17,7 @@
display label there.
-->
MokoSuiteBackup
- 02.58.17
+ 02.58.202026-06-02Moko Consultinghello@mokoconsulting.tech
diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.19.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.19.sql
new file mode 100644
index 00000000..ef0ad5d8
--- /dev/null
+++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.19.sql
@@ -0,0 +1 @@
+/* 02.58.19 — no schema changes */
diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.20.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.20.sql
new file mode 100644
index 00000000..5fdbdc3b
--- /dev/null
+++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.20.sql
@@ -0,0 +1 @@
+/* 02.58.20 — no schema changes */
diff --git a/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php b/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php
index cda1f601..0442b4bb 100644
--- a/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php
+++ b/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php
@@ -85,12 +85,14 @@ class AjaxController extends BaseController
}
/**
- * Mark the pre-update backup as satisfied for this session.
+ * Mark the pre-action backup as satisfied for this session.
*
- * Called by the update-page modal after a successful on-demand backup so the
- * imminent server-side onExtensionBeforeUpdate backup is skipped — it sets
- * the same 10-minute throttle key the system plugin checks, preventing a
- * duplicate backup when the admin then clicks Joomla's Update button.
+ * Called by the full-screen backup screen after a successful backup so the
+ * imminent server-side onExtensionBeforeUpdate / onExtensionBeforeUninstall
+ * backup is skipped — it arms the same 10-minute throttle keys the system
+ * plugin checks, preventing a duplicate backup when the update / uninstall
+ * then proceeds. Arms BOTH keys so a single ack covers whichever action the
+ * full-screen backup was fronting.
* POST: task=ajax.preupdateAck
*/
public function preupdateAck(): void
@@ -107,10 +109,13 @@ class AjaxController extends BaseController
return;
}
- // Same key + semantics as plg_system_mokosuitebackup::runPreActionBackup().
- Factory::getSession()->set('mokosuitebackup.preaction_backup_before_update', time());
+ // Same keys + semantics as plg_system_mokosuitebackup::runPreActionBackup().
+ $session = Factory::getSession();
+ $now = time();
+ $session->set('mokosuitebackup.preaction_backup_before_update', $now);
+ $session->set('mokosuitebackup.preaction_backup_before_uninstall', $now);
- $this->sendJson(['error' => false, 'message' => 'Pre-update backup acknowledged']);
+ $this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']);
}
/**
diff --git a/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php b/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php
index 811a8464..6ced4f5a 100644
--- a/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php
+++ b/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php
@@ -217,6 +217,7 @@ class SteppedBackupEngine
'progress' => 100,
'message' => 'Backup complete: ' . $session->archiveName,
'done' => true,
+ 'record_id' => $session->recordId,
];
}
@@ -228,6 +229,7 @@ class SteppedBackupEngine
'progress' => $session->getProgress(),
'message' => $session->statusMessage,
'done' => $session->phase === 'complete',
+ 'record_id' => $session->recordId,
];
} catch (\Throwable $e) {
$session->log('FATAL: ' . $e->getMessage());
diff --git a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php
index dab4defb..0faeb92d 100644
--- a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php
+++ b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php
@@ -64,6 +64,7 @@ $config = [
'description' => $this->description,
'returnUrl' => $safeReturnUrl,
'dashboardUrl' => $dashboardUrl,
+ 'recordUrl' => Route::_('index.php?option=com_mokosuitebackup&view=backup&id=__MSBID__', false),
'autostart' => $this->autostart,
'labels' => [
'starting' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'),
@@ -74,6 +75,7 @@ $config = [
'retry' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY'),
'continue' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY'),
'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'),
+ 'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'),
'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'),
],
];
@@ -105,6 +107,7 @@ $config = [
+
@@ -119,6 +122,7 @@ $config = [
var CFG = ;
var L = CFG.labels || {};
var running = false;
+ var lastRecordId = 0;
var el = {
bar: document.getElementById('msb-rb-bar'),
@@ -127,6 +131,7 @@ $config = [
title: document.getElementById('msb-rb-title'),
actions: document.getElementById('msb-rb-actions'),
retry: document.getElementById('msb-rb-retry'),
+ record: document.getElementById('msb-rb-record'),
continue: document.getElementById('msb-rb-continue'),
dashboard: document.getElementById('msb-rb-dashboard')
};
@@ -161,18 +166,32 @@ $config = [
node.classList.remove('d-none');
}
- function onComplete() {
+ async function onComplete() {
el.bar.classList.remove('progress-bar-striped', 'progress-bar-animated');
el.bar.classList.add('bg-success');
setBar(100, false);
el.title.textContent = L.complete || 'Backup complete';
+ /* Arm the pre-action throttles so a following server-side onExtension
+ Before(Update|Uninstall) backup is skipped (no duplicate backup).
+ Awaited so it lands before we hand back to the update/uninstall. */
+ try { await post({ task: 'ajax.preupdateAck' }); } catch (e) {}
+
if (CFG.returnUrl) {
+ /* Pre-update / pre-uninstall flow: continue the original action.
+ No "view record" button here — we're handing back to Joomla. */
setStatus(L.continuing || 'Continuing…');
window.setTimeout(function () { window.location.href = CFG.returnUrl; }, 1200);
} else {
+ /* Manual "Backup Now": offer to view the record that was just made. */
setStatus(L.complete || 'Backup complete');
showActions();
+
+ if (lastRecordId > 0 && CFG.recordUrl) {
+ showBtn(el.record, L.viewRecord || 'View backup record',
+ CFG.recordUrl.replace('__MSBID__', String(lastRecordId)));
+ }
+
showBtn(el.dashboard, L.dashboard || 'Back to dashboard', CFG.dashboardUrl);
}
}
@@ -219,6 +238,7 @@ $config = [
setBar(init.progress, true);
setPhase(init.phase || '');
setStatus(init.message || (L.running || 'Backing up…'));
+ if (init.record_id) { lastRecordId = init.record_id; }
var done = false;
while (!done) {
@@ -231,11 +251,12 @@ $config = [
setBar(step.progress, true);
setPhase(step.phase || '');
setStatus(step.message || '');
+ if (step.record_id) { lastRecordId = step.record_id; }
done = step.done || false;
}
running = false;
- onComplete();
+ await onComplete();
} catch (err) {
onError(err && err.message ? err.message : String(err));
}
diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml
index 98be043e..a5b2f370 100644
--- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml
+++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml
@@ -8,7 +8,7 @@
-->
Module - MokoSuiteBackup - cPanel
- 02.58.17
+ 02.58.202026-06-23Moko Consultinghello@mokoconsulting.tech
diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml
index 14ac64c0..47ea83a8 100644
--- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml
+++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml
@@ -7,7 +7,7 @@
-->
Action Log - MokoSuiteBackup
- 02.58.17
+ 02.58.202026-06-04Moko Consultinghello@mokoconsulting.tech
diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml
index 230da13f..3aa816f5 100644
--- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml
+++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml
@@ -7,7 +7,7 @@
-->
Console - MokoSuiteBackup
- 02.58.17
+ 02.58.202026-06-04Moko Consultinghello@mokoconsulting.tech
diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml
index 99402c16..a438b8aa 100644
--- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml
+++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml
@@ -7,7 +7,7 @@
-->
Content - MokoSuiteBackup
- 02.58.17
+ 02.58.202026-06-04Moko Consultinghello@mokoconsulting.tech
diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml
index 056d88da..0b68be93 100644
--- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml
+++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml
@@ -1,7 +1,7 @@
Quick Icon - MokoSuiteBackup
- 02.58.17
+ 02.58.202026-06-02Moko Consultinghello@mokoconsulting.tech
diff --git a/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js b/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js
new file mode 100644
index 00000000..1153e59a
--- /dev/null
+++ b/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js
@@ -0,0 +1,142 @@
+/**
+ * MokoSuiteBackup — full-screen backup before an EXTENSION update / uninstall.
+ *
+ * Injected by plg_system_mokosuitebackup on the Extensions → Update and
+ * Extensions → Manage pages. Unlike the core Joomla update (which has a
+ * server-side page load we can redirect), com_installer's update.update and
+ * manage.remove are POST actions with a CSRF token and a checked cid[] list —
+ * so we intercept CLIENT-SIDE: capture the selection, go to the full-screen
+ * backup page, and on return restore the selection and re-submit the real POST
+ * form (fresh token) so the update/uninstall proceeds.
+ *
+ * The full-screen page arms the pre-action throttle on success (ajax.preupdate
+ * Ack), so the server-side onExtensionBefore(Update|Uninstall) backup is then
+ * skipped — no duplicate backup.
+ *
+ * Config: Joomla.getOptions('plg_system_mokosuitebackup.installer').
+ */
+(function () {
+ 'use strict';
+
+ var cfg = (window.Joomla && Joomla.getOptions)
+ ? Joomla.getOptions('plg_system_mokosuitebackup.installer', null)
+ : null;
+
+ if (!cfg || !cfg.runbackupUrl) {
+ return;
+ }
+
+ var STORAGE_KEY = 'msbInstallerBackup';
+
+ /* Toolbar tasks to intercept, gated by which feature is enabled. */
+ var TASKS = {};
+ if (cfg.backupBeforeUpdate) { TASKS['update.update'] = true; }
+ if (cfg.backupBeforeUninstall) { TASKS['manage.remove'] = true; }
+
+ function checkedCids() {
+ var form = document.getElementById('adminForm');
+ var cids = [];
+
+ if (form) {
+ form.querySelectorAll('input[name="cid[]"]:checked').forEach(function (b) {
+ cids.push(b.value);
+ });
+ }
+
+ return cids;
+ }
+
+ /* On return from the backup screen: restore the selection and re-submit. */
+ function resumeIfNeeded() {
+ var params = new URLSearchParams(window.location.search);
+
+ if (params.get('msb_resume') !== '1') {
+ return;
+ }
+
+ var raw = null;
+ try { raw = sessionStorage.getItem(STORAGE_KEY); } catch (e) {}
+ try { sessionStorage.removeItem(STORAGE_KEY); } catch (e) {}
+
+ if (!raw) {
+ return;
+ }
+
+ var data;
+ try { data = JSON.parse(raw); } catch (e) { data = null; }
+
+ if (!data || !data.task) {
+ return;
+ }
+
+ var form = document.getElementById('adminForm');
+
+ if (!form) {
+ return;
+ }
+
+ (data.cids || []).forEach(function (id) {
+ var box = form.querySelector('input[name="cid[]"][value="' + id + '"]');
+ if (box) { box.checked = true; }
+ });
+
+ /* Re-run the original toolbar action; the wrapper lets it through
+ because the resume flag is set. */
+ window.__msbResuming = true;
+
+ if (window.Joomla && typeof Joomla.submitbutton === 'function') {
+ Joomla.submitbutton(data.task);
+ } else if (typeof window.submitbutton === 'function') {
+ window.submitbutton(data.task);
+ }
+ }
+
+ /* Wrap Joomla.submitbutton to intercept the update/uninstall tasks. */
+ function wrapSubmit() {
+ var J = window.Joomla;
+
+ if (!J || typeof J.submitbutton !== 'function' || J.submitbutton.__msbWrapped) {
+ return;
+ }
+
+ var original = J.submitbutton;
+
+ var wrapped = function (task) {
+ /* Skip when resuming after a backup, when a backup already ran this
+ session (throttle window), or for tasks we don't handle. */
+ if (window.__msbResuming || cfg.recentBackup || !TASKS[task]) {
+ return original.apply(J, arguments);
+ }
+
+ var cids = checkedCids();
+
+ /* Nothing selected — let Joomla show its own "please select" notice
+ and don't run a pointless backup. */
+ if (!cids.length) {
+ return original.apply(J, arguments);
+ }
+
+ try {
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ task: task, cids: cids }));
+ } catch (e) {}
+
+ var ret = window.location.href;
+ ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1';
+
+ window.location.href = cfg.runbackupUrl
+ + '&profile_id=' + encodeURIComponent(cfg.profileId)
+ + '&returnurl=' + encodeURIComponent(btoa(ret));
+ };
+
+ wrapped.__msbWrapped = true;
+ J.submitbutton = wrapped;
+ }
+
+ /* Wrap as early as possible, and again once the DOM/core JS is ready. */
+ wrapSubmit();
+
+ document.addEventListener('DOMContentLoaded', function () {
+ wrapSubmit();
+ resumeIfNeeded();
+ });
+})();
diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml
index fb4be78e..401a85fe 100644
--- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml
+++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml
@@ -7,7 +7,7 @@
-->
System - MokoSuiteBackup
- 02.58.17
+ 02.58.202026-06-02Moko Consultinghello@mokoconsulting.tech
@@ -29,6 +29,10 @@
language/en-GB/plg_system_mokosuitebackup.sys.ini
+
+ js
+
+