Compare commits

..

21 Commits

Author SHA1 Message Date
gitea-actions[bot] 69c70d4480 chore(release): build 02.60.00 [skip ci] 2026-07-12 23:33:41 +00:00
jmiller 89f1869120 Merge pull request 'Release: dev → main (02.59.x)' (#253) from dev into main
Cascade Main -> Dev / Cascade main -> dev (push) Has been cancelled
Sync Workflows to Repos / sync (push) Has been cancelled
2026-07-12 23:33:28 +00:00
mokogitea-actions[bot] 0c845fe807 chore(version): pre-release bump to 02.59.09-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 21s
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been cancelled
2026-07-12 23:33:14 +00:00
jmiller a60f7ac032 Merge pull request 'docs(ajax): correct preupdateAck docblock' (#254) from docs/preupdateack-docblock into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Lint & Validate (pull_request) Has been cancelled
Joomla: Extension CI / Release Readiness Check (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-12 23:33:00 +00:00
jmiller f62b1ec72e @
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
docs(ajax): correct preupdateAck docblock to one-shot skip semantics

The docblock still described the removed 10-minute throttle ("arms BOTH
keys"). It now sets a one-shot, action-keyed skip flag consumed once by
the system plugin. Comment-only; no behaviour change.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 18:28:54 -05:00
mokogitea-actions[bot] 0562d3effd chore(version): pre-release bump to 02.59.08-dev [skip ci] 2026-07-12 23:25:09 +00:00
gitea-actions[bot] 1c5c511c06 chore(version): auto-bump patch 02.59.07-dev [skip ci] 2026-07-12 23:24:59 +00:00
jmiller 444cfa3fdb Merge remote-tracking branch 'origin/dev' into dev
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 17s
2026-07-12 18:24:40 -05:00
jmiller 1e6d9ba078 @
chore: back-merge main into dev (release 02.59.00 version stamps)

Reconciles main (released 02.59.00) into dev ahead of the next release.
All conflicts were VERSION-stamp comments/tags in manifests, SECURITY.md
and issue-branch.yml — kept dev's current 02.59.05 line. No functional
changes. Clears the dev -> main merge conflict for the release PR.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 18:24:12 -05:00
mokogitea-actions[bot] 9ee800b151 chore(version): pre-release bump to 02.59.06-dev [skip ci] 2026-07-12 23:18:54 +00:00
jmiller 25991c474b Merge pull request 'refactor(runbackup): move screen CSS into the Web Asset Manager' (#252) from fix/runbackup-css-wam into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
2026-07-12 23:18:41 +00:00
mokogitea-actions[bot] 059cbfe058 chore(version): pre-release bump to 02.59.05-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-12 23:18:27 +00:00
jmiller d658019007 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
refactor(runbackup): move screen CSS into the Web Asset Manager

All full-screen backup CSS now loads through Joomla WAM instead of an
inline <style> block (and the redundant inline style="width:0"):

- New media/css/runbackup.css holds the whole stylesheet.
- Registered as asset com_mokosuitebackup.runbackup in joomla.asset.json.
- tmpl/runbackup/default.php loads it via
  getDocument()->getWebAssetManager()->useStyle(com_mokosuitebackup.runbackup),
  matching the existing com_mokosuitebackup.admin pattern.

The css folder already ships via the manifest <folder>css</folder>, so no
manifest change is needed.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 18:18:08 -05:00
mokogitea-actions[bot] 8237f85056 chore(version): pre-release bump to 02.59.04-dev [skip ci] 2026-07-12 23:14:28 +00:00
jmiller 14cc00f93f Merge pull request 'style(runbackup): black backdrop on the full-screen backup screen' (#251) from fix/runbackup-black-bg into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
2026-07-12 23:14:15 +00:00
mokogitea-actions[bot] d1ccb8d419 chore(version): pre-release bump to 02.59.03-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-12 23:14:09 +00:00
jmiller 8a783aa25a @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
style(runbackup): black backdrop on the full-screen backup screen

Set html/body background to #000 so the full-screen pre-update / Backup
Now progress screen runs on a black backdrop (the card + Atum-coloured
progress bar sit on top).

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 18:13:51 -05:00
mokogitea-actions[bot] d9d6ee9181 chore(version): pre-release bump to 02.59.02-dev [skip ci] 2026-07-12 22:48:45 +00:00
jmiller f02c4d4c9d Merge pull request 'fix: pre-action backup fires for every extension (not one per 10 min)' (#250) from fix/preaction-per-action-backup into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 17s
2026-07-12 22:48:33 +00:00
mokogitea-actions[bot] 2df4b280eb chore(version): pre-release bump to 02.59.01-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-12 22:48:27 +00:00
jmiller 3eb2562abd @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
fix: pre-action backup fires for every extension, not one per 10 min

Root cause of "pre-update backup only fires on the backup extension":
a coarse 600s session throttle. Whenever ANY pre-action backup ran
(including updating MokoSuiteBackup itself, or a core Joomla update), the
key mokosuitebackup.preaction_backup_before_update was armed for 10
minutes. onBeforeCompileHead read it as `recentBackup` and DISABLED the
client-side interceptor for all extensions, and runPreActionBackup was
throttled by the same window. So after updating one extension, every
other extension update silently skipped its backup for 10 minutes.

Replace the time window with "one backup per Update action":
- runPreActionBackup dedupes per-request (a batch update fires the event
  once per extension in a single request -> back up once) via instance
  flags, and consumes a ONE-SHOT session skip flag for the re-fired
  client update/uninstall (skip once, next distinct action backs up).
- ajax.preupdateAck sets the one-shot flag for the SPECIFIC action
  (update|uninstall), passed from installer-backup.js via &msb_action and
  surfaced by Runbackup HtmlView -> CFG.action. A core update passes no
  action, so it never suppresses an extension backup.
- onBeforeCompileHead drops the recentBackup time-suppression (the
  re-fire loop is already guarded client-side by window.__msbResuming).
- The core Joomla update path uses its own key
  (mokosuitebackup.core_update_backed_up), decoupled from extensions.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 17:48:09 -05:00
30 changed files with 202 additions and 99 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Automation
# VERSION: 02.59.00
# VERSION: 02.60.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+4 -6
View File
@@ -1,16 +1,19 @@
# Changelog
## [Unreleased]
## [02.60.00] --- 2026-07-12
## [02.59.00] --- 2026-07-12
### Fixed
- Pre-update backup now fires for **every** extension update/uninstall, not just the first one in a 10-minute window. A coarse 600-second session throttle (armed after any pre-action backup — even after updating MokoSuiteBackup itself or a core Joomla update) was disabling the client-side interceptor **and** the server-side fallback for *all* extensions for 10 minutes, so distinct updates silently went un-backed-up (looked like "only the backup extension triggers it"). Replaced with **one backup per Update action**: a single batch/click still backs up once (deduped per-request), the client re-fire is skipped via a **one-shot** action-keyed flag (consumed once), and the core-update path uses its own dedicated key so it no longer suppresses extension backups.
- Pre-update backup on the **Joomla Update** page is now **seamless** (Akeeba-style): after the full-screen backup runs, the plugin auto-ticks Joomla's "I have taken a backup" checkbox and clicks Install, so the update continues automatically instead of stopping for a second manual click.
- Pre-update full-screen backup now actually fires on the **Joomla Update** page. The "Install the update" button posts to `option=com_joomlaupdate&layout=update` (Joomla's confirm/updating page) — carrying **only `layout=update`**, no `view`/`task` — so the previous match on `view=update`/`update.install` never triggered. The plugin now intercepts `layout=update` (as well as `view=update` and the legacy `update.install`) and returns the browser there flagged `is_backed_up=1`.
### Added
- **Snapshot transfer + master→slave injection (#237):** download a content snapshot as a portable **`.msbsnap`** file (zip of `manifest.json` + `snapshot.json` + a sha256 integrity check); **import** one on another site via a new Import button on the Snapshots page (materialises a local snapshot record you can then restore); and a **direct injection API**`POST /api/index.php/v1/mokosuitebackup/snapshot/inject` — so a **master** site can push a snapshot straight into a **slave**'s Web Services API. Two conflict modes ship now: **overwrite** (replace matching items by ID — master wins) and **create** (skip existing, add only new), selectable per inject call or defaulted in **Options → Snapshot Transfer**. A receiving site must opt in via the new *Allow Snapshot Injection* setting. A third **duplicate** mode inserts everything as brand-new records with **remapped IDs** (articles, categories, modules — plus their tags, custom-field values and featured flags) using Joomla's Table API so assets/UCM/nested-sets stay valid; each item is inserted defensively (a bad item is skipped and logged, never fatal).
- 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.
- 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 deduped to one backup per Update action.
- 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)
@@ -61,8 +64,3 @@
### Fixed
- Package `postflight` now removes the orphaned `com_component-mokosuitebackup` registration (stray extension record, admin menu items, schema rows, and `administrator/` folder) left behind by the old "Type - Name" component `<name>` — self-healing on update so affected sites reconcile to the real `com_mokosuitebackup`. (#213 follow-up)
## [02.56.08] --- 2026-07-05
### Fixed
- Component manifest `<name>` reverted to the element-safe `MokoSuiteBackup`. The "Type - Name" convention derived element `com_component-mokosuitebackup`, registering a duplicate component and leaving the real `com_mokosuitebackup` orphaned on the old version. (#213)
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md
VERSION: 02.59.00
VERSION: 02.60.00
BRIEF: Security vulnerability reporting and handling policy
-->
@@ -0,0 +1,84 @@
/*
* @package MokoSuiteBackup
* @subpackage com_mokosuitebackup
* @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 (view=runbackup). Loaded via the Joomla
* Web Asset Manager (asset com_mokosuitebackup.runbackup).
*/
/* Full-screen backup runs on a black backdrop. */
html,
body {
background-color: #000 !important;
}
/* Colours are pulled from the Atum admin template's own CSS custom properties
so the screen matches whatever theme the site runs, with Bootstrap and then
a literal as progressive fallbacks. */
.msb-runbackup {
--msb-accent: var(--template-link-color, var(--bs-link-color, #2a69b8));
--msb-accent2: var(--template-special-color, var(--msb-accent));
--msb-success: var(--template-success-color, var(--bs-success, #2f7d32));
--msb-danger: var(--template-danger-color, var(--bs-danger, #c52128));
--msb-track: var(--template-bg-light, var(--bs-gray-200, #edf0f5));
padding: 0 1rem;
}
/* overflow:hidden clips the full-bleed bar to the card's rounded corners. */
.msb-runbackup .card {
overflow: hidden;
}
.msb-rb-progress {
height: 2.5rem;
background: var(--msb-track);
overflow: hidden;
}
.msb-rb-bar {
position: relative;
height: 100%;
width: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
font-size: .95rem;
white-space: nowrap;
background-color: var(--msb-accent);
background-image: linear-gradient(90deg, var(--msb-accent), var(--msb-accent2));
transition: width .35s ease;
}
.msb-rb-bar.is-animated::after {
content: "";
position: absolute;
inset: 0;
background-image: linear-gradient(45deg, rgba(255, 255, 255, .18) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .18) 50%, rgba(255, 255, 255, .18) 75%, transparent 75%, transparent);
background-size: 1rem 1rem;
animation: msb-rb-stripes 1s linear infinite;
}
.msb-rb-bar.is-success {
background-image: none;
background-color: var(--msb-success);
}
.msb-rb-bar.is-danger {
background-image: none;
background-color: var(--msb-danger);
}
@keyframes msb-rb-stripes {
from {
background-position: 1rem 0;
}
to {
background-position: 0 0;
}
}
@@ -9,6 +9,11 @@
"name": "com_mokosuitebackup.admin",
"type": "style",
"uri": "com_mokosuitebackup/css/admin.css"
},
{
"name": "com_mokosuitebackup.runbackup",
"type": "style",
"uri": "com_mokosuitebackup/css/runbackup.css"
}
]
}
@@ -17,7 +17,7 @@
display label there.
-->
<name>MokoSuiteBackup</name>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -0,0 +1 @@
/* 02.59.01 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.02 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.03 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.04 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.05 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.06 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.07 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.08 — no schema changes */
@@ -0,0 +1 @@
/* 02.59.09 — no schema changes */
@@ -0,0 +1 @@
/* 02.60.00 — no schema changes */
@@ -89,11 +89,11 @@ class AjaxController extends BaseController
*
* 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
* backup is skipped once — it sets a ONE-SHOT skip flag (keyed by the
* `action` the backup was fronting) that plg_system_mokosuitebackup
* consumes on the re-fired update/uninstall request, so exactly that one
* action is not double-backed-up while later, distinct actions still are.
* POST: task=ajax.preupdateAck&action=update|uninstall
*/
public function preupdateAck(): void
{
@@ -109,11 +109,21 @@ class AjaxController extends BaseController
return;
}
// Same keys + semantics as plg_system_mokosuitebackup::runPreActionBackup().
/* Set a ONE-SHOT skip flag for the specific action the full-screen backup
was fronting. runPreActionBackup() consumes it on the re-fired
update/uninstall request (once), so the synchronous backup is skipped
that one time but the NEXT distinct action still backs up. Keyed by
action so an update ack never suppresses a later, unrelated uninstall.
For a core Joomla update (no action) neither flag is set — a core
update doesn't fire onExtensionBefore(Update|Uninstall). */
$action = $this->app->getInput()->getCmd('action', '');
$session = Factory::getSession();
$now = time();
$session->set('mokosuitebackup.preaction_backup_before_update', $now);
$session->set('mokosuitebackup.preaction_backup_before_uninstall', $now);
if ($action === 'update') {
$session->set('mokosuitebackup.skip_preaction_backup_before_update', true);
} elseif ($action === 'uninstall') {
$session->set('mokosuitebackup.skip_preaction_backup_before_uninstall', true);
}
$this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']);
}
@@ -40,6 +40,9 @@ class HtmlView extends BaseHtmlView
public bool $autostart = true;
/** Which action the backup fronts ('update' | 'uninstall' | ''); drives the completion ack skip flag. */
public string $action = '';
public function display($tpl = null): void
{
$input = Factory::getApplication()->getInput();
@@ -48,6 +51,7 @@ class HtmlView extends BaseHtmlView
$this->description = $input->getString('description', '');
$this->returnUrl = $input->getRaw('returnurl', '');
$this->autostart = (bool) $input->getInt('autostart', 1);
$this->action = $input->getCmd('msb_action', '');
if ($this->profileId <= 0) {
$this->profileId = 1;
@@ -71,6 +71,7 @@ $config = [
'profileId' => $this->profileId,
'description' => $this->description,
'returnUrl' => $safeReturnUrl,
'action' => $this->action,
'dashboardUrl' => $dashboardUrl,
'recordUrl' => Route::_('index.php?option=com_mokosuitebackup&view=backup&id=__MSBID__', false),
'autostart' => $this->autostart,
@@ -87,53 +88,11 @@ $config = [
'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'),
],
];
// All screen CSS loads via the Joomla Web Asset Manager (media/joomla.asset.json),
// not an inline <style> block.
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbackup');
?>
<style>
/* Colours are pulled from the Atum admin template's own CSS custom
properties so the screen matches whatever theme the site runs, with
Bootstrap and then a literal as progressive fallbacks. */
.msb-runbackup {
--msb-accent: var(--template-link-color, var(--bs-link-color, #2a69b8));
--msb-accent2: var(--template-special-color, var(--msb-accent));
--msb-success: var(--template-success-color, var(--bs-success, #2f7d32));
--msb-danger: var(--template-danger-color, var(--bs-danger, #c52128));
--msb-track: var(--template-bg-light, var(--bs-gray-200, #edf0f5));
padding: 0 1rem;
}
/* overflow:hidden clips the full-bleed bar to the card's rounded corners. */
.msb-runbackup .card { overflow: hidden; }
.msb-rb-progress {
height: 2.5rem;
background: var(--msb-track);
overflow: hidden;
}
.msb-rb-bar {
height: 100%;
width: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
font-size: .95rem;
white-space: nowrap;
background-color: var(--msb-accent);
background-image: linear-gradient(90deg, var(--msb-accent), var(--msb-accent2));
transition: width .35s ease;
}
.msb-rb-bar.is-animated::after {
content: "";
position: absolute;
inset: 0;
background-image: linear-gradient(45deg, rgba(255,255,255,.18) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.18) 50%, rgba(255,255,255,.18) 75%, transparent 75%, transparent);
background-size: 1rem 1rem;
animation: msb-rb-stripes 1s linear infinite;
}
.msb-rb-bar { position: relative; }
.msb-rb-bar.is-success { background-image: none; background-color: var(--msb-success); }
.msb-rb-bar.is-danger { background-image: none; background-color: var(--msb-danger); }
@keyframes msb-rb-stripes { from { background-position: 1rem 0; } to { background-position: 0 0; } }
</style>
<div class="msb-runbackup">
<div class="card shadow-sm">
<div class="card-body p-4 pb-3">
@@ -164,7 +123,7 @@ $config = [
<!-- Full-bleed progress bar, edge-to-edge along the bottom of the card. -->
<div class="msb-rb-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div id="msb-rb-bar" class="msb-rb-bar is-animated" style="width:0;">0%</div>
<div id="msb-rb-bar" class="msb-rb-bar is-animated">0%</div>
</div>
</div>
</div>
@@ -227,10 +186,11 @@ $config = [
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).
/* Set the one-shot skip flag for THIS action so the following
server-side onExtensionBefore(Update|Uninstall) backup is skipped once
(no duplicate backup) without suppressing later, distinct actions.
Awaited so it lands before we hand back to the update/uninstall. */
try { await post({ task: 'ajax.preupdateAck' }); } catch (e) {}
try { await post({ task: 'ajax.preupdateAck', action: CFG.action || '' }); } catch (e) {}
if (CFG.returnUrl) {
/* Pre-update / pre-uninstall flow: continue the original action.
@@ -8,7 +8,7 @@
-->
<extension type="module" client="administrator" method="upgrade">
<name>Module - MokoSuiteBackup - cPanel</name>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-23</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="actionlog" method="upgrade">
<name>Action Log - MokoSuiteBackup</name>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="console" method="upgrade">
<name>Console - MokoSuiteBackup</name>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="content" method="upgrade">
<name>Content - MokoSuiteBackup</name>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="quickicon" method="upgrade">
<name>Quick Icon - MokoSuiteBackup</name>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -73,6 +73,10 @@
var ret = window.location.href;
ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1';
/* Tell the backup screen which action it is fronting so its completion
ack sets the matching one-shot skip flag (and not the other). */
var action = (task === 'update.update') ? 'update' : 'uninstall';
/* Build the redirect BEFORE we swallow the click. btoa() throws on
non-Latin1 input (e.g. a UTF-8 filter value in the query string), so
encode as UTF-8 first; if it still fails, bail WITHOUT preventing the
@@ -81,6 +85,7 @@
try {
target = cfg.runbackupUrl
+ '&profile_id=' + encodeURIComponent(cfg.profileId)
+ '&msb_action=' + action
+ '&returnurl=' + encodeURIComponent(btoa(unescape(encodeURIComponent(ret))));
} catch (x) {
return;
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteBackup</name>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -28,6 +28,12 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
{
protected $autoloadLanguage = true;
/** @var array<string,bool> Per-request guard: at most one pre-action backup per request (dedupes a batch update). */
private array $preactionRan = [];
/** @var array<string,bool> Per-request cache of the one-shot "client already backed up" skip flag. */
private array $preactionSkip = [];
public static function getSubscribedEvents(): array
{
return [
@@ -223,23 +229,20 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
return;
}
// Already backed up this session (throttle window)? Then the JS should
// let the action proceed without a fresh backup.
$session = Factory::getSession();
$throttleK = $view === 'update'
? 'mokosuitebackup.preaction_backup_before_update'
: 'mokosuitebackup.preaction_backup_before_uninstall';
$lastRun = (int) $session->get($throttleK, 0);
$recent = $lastRun > 0 && (time() - $lastRun) < 600;
$doc->getWebAssetManager()->useScript('core');
/* No time-based "recently backed up" suppression here: each distinct
Update/Uninstall click should get its own full-screen backup. The
re-fired click after a backup is guarded client-side by
window.__msbResuming, and the synchronous server-side fallback is
deduped per-request + by a one-shot skip flag (see runPreActionBackup),
so there is no double backup without blocking distinct actions. */
$doc->addScriptOptions('plg_system_mokosuitebackup.installer', [
'runbackupUrl' => Route::_('index.php?option=com_mokosuitebackup&view=runbackup&tmpl=component&autostart=1', false),
'profileId' => (int) $params->get('default_profile', 1),
'backupBeforeUpdate' => $view === 'update' && (bool) $beforeUpdate,
'backupBeforeUninstall' => $view === 'manage' && (bool) $beforeUninst,
'recentBackup' => $recent,
'recentBackup' => false,
]);
$doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/installer-backup.js', [], ['defer' => true]);
@@ -313,16 +316,20 @@ JS;
$session = Factory::getSession();
// Returned from the backup screen: arm the throttle so the synchronous
// onExtensionBeforeUpdate backup doesn't run again, then let it proceed.
// Returned from the backup screen: mark this core update backed up so we
// don't redirect to the backup screen again on a page revisit, then let
// it proceed. Uses a DEDICATED core-update key — a core update does not
// fire onExtensionBefore(Update|Uninstall), so it must not touch the
// extension pre-action flags (which would suppress extension backups).
if ((int) $input->getInt('is_backed_up', 0) === 1) {
$session->set('mokosuitebackup.preaction_backup_before_update', time());
$session->set('mokosuitebackup.core_update_backed_up', time());
return;
}
// Backed up recently already — let the update proceed.
$lastRun = (int) $session->get('mokosuitebackup.preaction_backup_before_update', 0);
// Backed up this core update recently already — let the update proceed
// without redirecting to the backup screen again.
$lastRun = (int) $session->get('mokosuitebackup.core_update_backed_up', 0);
if ($lastRun > 0 && (time() - $lastRun) < 600) {
return;
@@ -525,17 +532,37 @@ JS;
return;
}
// Throttle: only run once per 10 minutes to prevent duplicate
// backups when multiple extensions are updated in a batch
$session = Factory::getSession();
$sessionKey = 'mokosuitebackup.preaction_' . $paramName;
$lastRun = $session->get($sessionKey, 0);
/* One backup per Update action — NOT a 10-minute time throttle. The old
600s window suppressed every extension's pre-action backup for 10
minutes after any single one ran (e.g. after updating this very
extension), so distinct updates silently went un-backed-up. Instead:
if (time() - $lastRun < 600) {
(a) dedupe within a single request — a batch update fires this handler
once per extension, all in the same request, so back up only once;
(b) honour a one-shot session skip flag set by ajax.preupdateAck when
the client-side full-screen backup already ran and is re-firing the
real update/uninstall — consumed once, so the NEXT distinct action
backs up again. */
if (!empty($this->preactionRan[$paramName])) {
return;
}
$session->set($sessionKey, time());
$session = Factory::getSession();
$skipKey = 'mokosuitebackup.skip_preaction_' . $paramName;
if (!array_key_exists($paramName, $this->preactionSkip)) {
$this->preactionSkip[$paramName] = (bool) $session->get($skipKey, false);
$session->set($skipKey, false);
}
if ($this->preactionSkip[$paramName]) {
// Client already backed up; skip this whole re-fired (possibly batch) request.
$this->preactionRan[$paramName] = true;
return;
}
$this->preactionRan[$paramName] = true;
$profileId = (int) $params->get('default_profile', 1);
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="task" method="upgrade">
<name>Task - MokoSuiteBackup</name>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteBackup</name>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+1 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuiteBackup</name>
<packagename>mokosuitebackup</packagename>
<version>02.59.00</version>
<version>02.60.00</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>