Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbc8627fb7 | |||
| f1dfc18f3d | |||
| 20fcd1c39c | |||
| 3fb82bc03c | |||
| 499436a8d0 | |||
| a004b9c0d9 | |||
| 5b1cf9ea8e | |||
| 86fe470158 | |||
| a6e196ffd0 | |||
| d43b9405c0 | |||
| 8506573898 | |||
| 6ad5046f82 | |||
| 20a760097b | |||
| b378794a05 | |||
| dc23f4dd5e | |||
| 9a5f4dbbb2 | |||
| 45066237b0 | |||
| 2296c2f754 | |||
| 7416c30925 | |||
| 186a75681a | |||
| 0a60436f3d | |||
| a2362d5400 | |||
| 6f1d270818 | |||
| 7aa1f2eb43 | |||
| 4339c223a1 | |||
| cb56430e37 | |||
| 67611604e2 | |||
| 00c1add0cd | |||
| 1e3f963beb | |||
| c280229b90 | |||
| fbf0a2de76 | |||
| 30b8e66eca | |||
| 83b261db02 | |||
| e0285f112f | |||
| f5538ade88 | |||
| 2d27385ac9 | |||
| a3eb713d81 | |||
| 7106ece393 | |||
| 394ad0d607 | |||
| f5bcc7c457 | |||
| 69c70d4480 | |||
| 89f1869120 | |||
| 0c845fe807 | |||
| a60f7ac032 | |||
| f62b1ec72e | |||
| 0562d3effd | |||
| 1c5c511c06 | |||
| 444cfa3fdb | |||
| 1e6d9ba078 | |||
| 9ee800b151 | |||
| 25991c474b | |||
| 059cbfe058 | |||
| d658019007 | |||
| 8237f85056 | |||
| 14cc00f93f | |||
| d1ccb8d419 | |||
| 8a783aa25a | |||
| d9d6ee9181 | |||
| f02c4d4c9d | |||
| 2df4b280eb | |||
| 3eb2562abd |
@@ -7,7 +7,7 @@ Full-site backup and restore for Joomla — database, files, and configuration.
|
|||||||
| Field | Value |
|
| Field | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Package** | `pkg_mokosuitebackup` |
|
| **Package** | `pkg_mokosuitebackup` |
|
||||||
| **Language** | PHP 8.1+ |
|
| **Language** | PHP 8.3+ |
|
||||||
| **Branch** | develop on `dev`, merge to `main` (protected) |
|
| **Branch** | develop on `dev`, merge to `main` (protected) |
|
||||||
| **Wiki** | [MokoSuiteBackup Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup/wiki) |
|
| **Wiki** | [MokoSuiteBackup Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup/wiki) |
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ Joomla **package** with four sub-extensions:
|
|||||||
### com_mokosuitebackup (Component)
|
### com_mokosuitebackup (Component)
|
||||||
- Admin backend for managing backup profiles and records
|
- Admin backend for managing backup profiles and records
|
||||||
- Backup engine: `Engine/BackupEngine`, `Engine/DatabaseDumper`, `Engine/FileScanner`, `Engine/Archiver`
|
- Backup engine: `Engine/BackupEngine`, `Engine/DatabaseDumper`, `Engine/FileScanner`, `Engine/Archiver`
|
||||||
- Joomla 4/5 MVC: Controllers, Models, Views, Tables
|
- Joomla 6 MVC: Controllers, Models, Views, Tables
|
||||||
- Namespace: `Joomla\Component\MokoSuiteBackup\Administrator`
|
- Namespace: `Joomla\Component\MokoSuiteBackup\Administrator`
|
||||||
- DB tables: `#__mokosuitebackup_profiles`, `#__mokosuitebackup_records`
|
- DB tables: `#__mokosuitebackup_profiles`, `#__mokosuitebackup_records`
|
||||||
- CLI: `cli/mokosuitebackup.php` for cron-based backups
|
- CLI: `cli/mokosuitebackup.php` for cron-based backups
|
||||||
@@ -64,8 +64,8 @@ Joomla **package** with four sub-extensions:
|
|||||||
|
|
||||||
## Coding Standards
|
## Coding Standards
|
||||||
|
|
||||||
- PHP 8.1+ minimum
|
- PHP 8.3+ minimum (Joomla 6)
|
||||||
- Joomla 4/5 DI container pattern: `services/provider.php` → Extension class
|
- Joomla 6 DI container pattern: `services/provider.php` → Extension class
|
||||||
- Legacy stub `.php` file required for plugin loader but empty
|
- Legacy stub `.php` file required for plugin loader but empty
|
||||||
- `SubscriberInterface` for event subscription (not `on*` method naming)
|
- `SubscriberInterface` for event subscription (not `on*` method naming)
|
||||||
- `bind() → check() → store()` for Table operations (not `save()`)
|
- `bind() → check() → store()` for Table operations (not `save()`)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: MokoGitea.Workflow
|
# DEFGROUP: MokoGitea.Workflow
|
||||||
# INGROUP: mokocli.Automation
|
# INGROUP: mokocli.Automation
|
||||||
# VERSION: 02.59.00
|
# VERSION: 02.62.05
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
name: "Universal: Issue Branch"
|
name: "Universal: Issue Branch"
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ jobs:
|
|||||||
WORKFLOW="${{ github.event.workflow_run.name }}"
|
WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||||
URL="${{ github.event.workflow_run.html_url }}"
|
URL="${{ github.event.workflow_run.html_url }}"
|
||||||
|
|
||||||
curl -sS \
|
curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
|
||||||
-H "Title: ${REPO} released" \
|
-H "Title: ${REPO} released" \
|
||||||
-H "Tags: white_check_mark,package" \
|
-H "Tags: white_check_mark,package" \
|
||||||
-H "Priority: default" \
|
-H "Priority: default" \
|
||||||
-H "Click: ${URL}" \
|
-H "Click: ${URL}" \
|
||||||
-d "${WORKFLOW} completed successfully." \
|
-d "${WORKFLOW} completed successfully." \
|
||||||
"${NTFY_URL}/${NTFY_TOPIC}"
|
"${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
|
||||||
|
|
||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: github.event.workflow_run.conclusion == 'failure'
|
if: github.event.workflow_run.conclusion == 'failure'
|
||||||
@@ -61,10 +61,10 @@ jobs:
|
|||||||
WORKFLOW="${{ github.event.workflow_run.name }}"
|
WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||||
URL="${{ github.event.workflow_run.html_url }}"
|
URL="${{ github.event.workflow_run.html_url }}"
|
||||||
|
|
||||||
curl -sS \
|
curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
|
||||||
-H "Title: ${REPO} workflow failed" \
|
-H "Title: ${REPO} workflow failed" \
|
||||||
-H "Tags: x,warning" \
|
-H "Tags: x,warning" \
|
||||||
-H "Priority: high" \
|
-H "Priority: high" \
|
||||||
-H "Click: ${URL}" \
|
-H "Click: ${URL}" \
|
||||||
-d "${WORKFLOW} failed. Check the run for details." \
|
-d "${WORKFLOW} failed. Check the run for details." \
|
||||||
"${NTFY_URL}/${NTFY_TOPIC}"
|
"${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
|
||||||
|
|||||||
@@ -258,8 +258,9 @@ jobs:
|
|||||||
while IFS= read -r -d '' file; do
|
while IFS= read -r -d '' file; do
|
||||||
# Skip vendor, node_modules, and index.html stub files
|
# Skip vendor, node_modules, and index.html stub files
|
||||||
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
|
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
|
||||||
# Check first 10 lines for JEXEC or JPATH guard
|
# Scan the whole file for the JEXEC/JPATH guard: it is placed after
|
||||||
if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
|
# the SPDX/file-header docblock, which commonly runs past 20 lines.
|
||||||
|
if ! grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]" "$file"; then
|
||||||
echo "::error file=${file}::Missing JEXEC guard: ${file}"
|
echo "::error file=${file}::Missing JEXEC guard: ${file}"
|
||||||
ERRORS=$((ERRORS + 1))
|
ERRORS=$((ERRORS + 1))
|
||||||
fi
|
fi
|
||||||
|
|||||||
+8
-19
@@ -1,16 +1,23 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [02.62.00] --- 2026-07-13
|
||||||
|
|
||||||
|
## [02.61.00] --- 2026-07-13
|
||||||
|
|
||||||
|
## [02.60.00] --- 2026-07-12
|
||||||
|
|
||||||
|
|
||||||
## [02.59.00] --- 2026-07-12
|
## [02.59.00] --- 2026-07-12
|
||||||
|
|
||||||
### Fixed
|
### 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 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`.
|
- 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
|
### 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).
|
- **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.
|
- 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)
|
- **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)
|
- 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)
|
||||||
@@ -48,21 +55,3 @@
|
|||||||
### Fixed
|
### Fixed
|
||||||
- Admin menu / duplicate-component fallout: (a) the orphaned `com_component-mokosuitebackup` is now removed in **preflight** too, so its "Backup" menu no longer collides on install ("The alias backup is already being used"); (b) `ensureSubmenuItems()` **recreates the top-level "Backup" menu** if it was deleted, instead of giving up — so Backup no longer disappears from the admin menu (and the cPanel module / MokoSuiteClient can find it again); (c) the component menu label now uses the short constant `COM_MOKOJOOMBACKUP_SHORT`. (#213 fallout)
|
- Admin menu / duplicate-component fallout: (a) the orphaned `com_component-mokosuitebackup` is now removed in **preflight** too, so its "Backup" menu no longer collides on install ("The alias backup is already being used"); (b) `ensureSubmenuItems()` **recreates the top-level "Backup" menu** if it was deleted, instead of giving up — so Backup no longer disappears from the admin menu (and the cPanel module / MokoSuiteClient can find it again); (c) the component menu label now uses the short constant `COM_MOKOJOOMBACKUP_SHORT`. (#213 fallout)
|
||||||
- Installer no longer aborts on MySQL 8 with "This command is not supported in the prepared statement protocol yet" (error 1295). The legacy remote-column purge migration (`02.56.01.sql`) used `PREPARE`/`EXECUTE`/`DEALLOCATE`, which Joomla's installer rejects; the drop now runs in the package installer script via an INFORMATION_SCHEMA-gated plain `ALTER` (portable across MariaDB and MySQL 8). (#213 update path)
|
- Installer no longer aborts on MySQL 8 with "This command is not supported in the prepared statement protocol yet" (error 1295). The legacy remote-column purge migration (`02.56.01.sql`) used `PREPARE`/`EXECUTE`/`DEALLOCATE`, which Joomla's installer rejects; the drop now runs in the package installer script via an INFORMATION_SCHEMA-gated plain `ALTER` (portable across MariaDB and MySQL 8). (#213 update path)
|
||||||
|
|
||||||
## [02.57.00] --- 2026-07-05
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- `BackupRunner` service — a single synchronous entry point for backups that unifies final status and notification gating across the pre-update, web-cron, pre-install, scheduled-task and CLI triggers (delegates to `BackupEngine`; no double-notify). Returns a normalized result including the real complete/warning/fail status. Dashboard "Backup Now" (AJAX) unchanged; installer progress popup deferred to #196. (#214)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Release automation: the changelog-promote step is now idempotent — it only inserts a `## [version]` header when that version isn't already present, preventing the empty, duplicated version headers that repeated/same-version builds were producing. (workflow)
|
|
||||||
|
|
||||||
## [02.56.11] --- 2026-07-05
|
|
||||||
|
|
||||||
### 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
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
|
|||||||
INGROUP: Template-Joomla.Documentation
|
INGROUP: Template-Joomla.Documentation
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
||||||
PATH: /SECURITY.md
|
PATH: /SECURITY.md
|
||||||
VERSION: 02.59.00
|
VERSION: 02.62.05
|
||||||
BRIEF: Security vulnerability reporting and handling policy
|
BRIEF: Security vulnerability reporting and handling policy
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.1",
|
"php": ">=8.3",
|
||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"ext-pdo_mysql": "*",
|
"ext-pdo_mysql": "*",
|
||||||
|
|||||||
Submodule source/packages/MokoSuiteClient updated: 246f7bb10c...06c5ba15e5
@@ -349,6 +349,8 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
|
|||||||
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
|
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
|
||||||
COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
|
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_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
|
||||||
|
COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
|
||||||
|
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
|
||||||
|
|
||||||
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
|
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
|
||||||
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
|
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
|
|||||||
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
|
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
|
||||||
COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
|
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_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
|
||||||
|
COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
|
||||||
|
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
|
||||||
|
|
||||||
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
|
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
|
||||||
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
|
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* @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.
|
||||||
|
The card's gray background comes from a Bootstrap utility class on the
|
||||||
|
element itself (see the template), not a rule here. */
|
||||||
|
.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",
|
"name": "com_mokosuitebackup.admin",
|
||||||
"type": "style",
|
"type": "style",
|
||||||
"uri": "com_mokosuitebackup/css/admin.css"
|
"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.
|
display label there.
|
||||||
-->
|
-->
|
||||||
<name>MokoSuiteBackup</name>
|
<name>MokoSuiteBackup</name>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<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 */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.60.01 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.60.02 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.60.03 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.60.04 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.60.05 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.60.06 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.61.00 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.61.01 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.61.04 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.61.05 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.61.06 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.62.00 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.62.01 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.62.02 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.62.03 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.62.04 — no schema changes */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* 02.62.05 — no schema changes */
|
||||||
@@ -89,11 +89,11 @@ class AjaxController extends BaseController
|
|||||||
*
|
*
|
||||||
* Called by the full-screen backup screen after a successful backup so the
|
* Called by the full-screen backup screen after a successful backup so the
|
||||||
* imminent server-side onExtensionBeforeUpdate / onExtensionBeforeUninstall
|
* imminent server-side onExtensionBeforeUpdate / onExtensionBeforeUninstall
|
||||||
* backup is skipped — it arms the same 10-minute throttle keys the system
|
* backup is skipped once — it sets a ONE-SHOT skip flag (keyed by the
|
||||||
* plugin checks, preventing a duplicate backup when the update / uninstall
|
* `action` the backup was fronting) that plg_system_mokosuitebackup
|
||||||
* then proceeds. Arms BOTH keys so a single ack covers whichever action the
|
* consumes on the re-fired update/uninstall request, so exactly that one
|
||||||
* full-screen backup was fronting.
|
* action is not double-backed-up while later, distinct actions still are.
|
||||||
* POST: task=ajax.preupdateAck
|
* POST: task=ajax.preupdateAck&action=update|uninstall
|
||||||
*/
|
*/
|
||||||
public function preupdateAck(): void
|
public function preupdateAck(): void
|
||||||
{
|
{
|
||||||
@@ -109,11 +109,21 @@ class AjaxController extends BaseController
|
|||||||
return;
|
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();
|
$session = Factory::getSession();
|
||||||
$now = time();
|
|
||||||
$session->set('mokosuitebackup.preaction_backup_before_update', $now);
|
if ($action === 'update') {
|
||||||
$session->set('mokosuitebackup.preaction_backup_before_uninstall', $now);
|
$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']);
|
$this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -480,8 +480,8 @@ function actionPreflight(): array
|
|||||||
$checks[] = [
|
$checks[] = [
|
||||||
'label' => 'PHP Version',
|
'label' => 'PHP Version',
|
||||||
'value' => PHP_VERSION,
|
'value' => PHP_VERSION,
|
||||||
'ok' => version_compare(PHP_VERSION, '8.1', '>='),
|
'ok' => version_compare(PHP_VERSION, '8.3', '>='),
|
||||||
'hint' => 'Joomla 4/5 requires PHP 8.1+',
|
'hint' => 'Joomla 6 requires PHP 8.3+',
|
||||||
];
|
];
|
||||||
|
|
||||||
$checks[] = [
|
$checks[] = [
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class DashboardModel extends BaseDatabaseModel
|
|||||||
// PHP version
|
// PHP version
|
||||||
$checks[] = (object) [
|
$checks[] = (object) [
|
||||||
'label' => 'PHP Version',
|
'label' => 'PHP Version',
|
||||||
'status' => version_compare(PHP_VERSION, '8.1.0', '>='),
|
'status' => version_compare(PHP_VERSION, '8.3.0', '>='),
|
||||||
'detail' => PHP_VERSION,
|
'detail' => PHP_VERSION,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ class HtmlView extends BaseHtmlView
|
|||||||
|
|
||||||
public bool $autostart = true;
|
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
|
public function display($tpl = null): void
|
||||||
{
|
{
|
||||||
$input = Factory::getApplication()->getInput();
|
$input = Factory::getApplication()->getInput();
|
||||||
@@ -48,6 +51,7 @@ class HtmlView extends BaseHtmlView
|
|||||||
$this->description = $input->getString('description', '');
|
$this->description = $input->getString('description', '');
|
||||||
$this->returnUrl = $input->getRaw('returnurl', '');
|
$this->returnUrl = $input->getRaw('returnurl', '');
|
||||||
$this->autostart = (bool) $input->getInt('autostart', 1);
|
$this->autostart = (bool) $input->getInt('autostart', 1);
|
||||||
|
$this->action = $input->getCmd('msb_action', '');
|
||||||
|
|
||||||
if ($this->profileId <= 0) {
|
if ($this->profileId <= 0) {
|
||||||
$this->profileId = 1;
|
$this->profileId = 1;
|
||||||
|
|||||||
@@ -14,13 +14,18 @@ use Joomla\CMS\HTML\HTMLHelper;
|
|||||||
use Joomla\CMS\Language\Text;
|
use Joomla\CMS\Language\Text;
|
||||||
use Joomla\CMS\Router\Route;
|
use Joomla\CMS\Router\Route;
|
||||||
use Joomla\CMS\Session\Session;
|
use Joomla\CMS\Session\Session;
|
||||||
|
use Joomla\CMS\Uri\Uri;
|
||||||
|
|
||||||
HTMLHelper::_('behavior.formvalidator');
|
HTMLHelper::_('behavior.formvalidator');
|
||||||
HTMLHelper::_('behavior.keepalive');
|
HTMLHelper::_('behavior.keepalive');
|
||||||
HTMLHelper::_('bootstrap.modal');
|
HTMLHelper::_('bootstrap.modal');
|
||||||
|
|
||||||
/* Component CSS via the Web Asset Manager (declared in media/joomla.asset.json) */
|
/* Component CSS. Registered in media/joomla.asset.json, but this component's WAM
|
||||||
|
styles don't emit on the admin document (asset resolves without error yet no
|
||||||
|
<link> renders), so attach the external stylesheet directly — reliably output
|
||||||
|
via <jdoc:include type="styles" />. */
|
||||||
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.admin');
|
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.admin');
|
||||||
|
$this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebackup/css/admin.css');
|
||||||
|
|
||||||
$profileId = (int) $this->item->id;
|
$profileId = (int) $this->item->id;
|
||||||
$token = Session::getFormToken();
|
$token = Session::getFormToken();
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ $config = [
|
|||||||
'profileId' => $this->profileId,
|
'profileId' => $this->profileId,
|
||||||
'description' => $this->description,
|
'description' => $this->description,
|
||||||
'returnUrl' => $safeReturnUrl,
|
'returnUrl' => $safeReturnUrl,
|
||||||
|
'action' => $this->action,
|
||||||
'dashboardUrl' => $dashboardUrl,
|
'dashboardUrl' => $dashboardUrl,
|
||||||
'recordUrl' => Route::_('index.php?option=com_mokosuitebackup&view=backup&id=__MSBID__', false),
|
'recordUrl' => Route::_('index.php?option=com_mokosuitebackup&view=backup&id=__MSBID__', false),
|
||||||
'autostart' => $this->autostart,
|
'autostart' => $this->autostart,
|
||||||
@@ -85,57 +86,22 @@ $config = [
|
|||||||
'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'),
|
'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'),
|
||||||
'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'),
|
'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'),
|
||||||
'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'),
|
'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'),
|
||||||
|
'continueUpdate' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE'),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Screen CSS lives in an external media stylesheet (media/com_mokosuitebackup/
|
||||||
|
// css/runbackup.css), not an inline <style> block. It is registered in
|
||||||
|
// media/joomla.asset.json, but this component's Web Asset Manager styles do not
|
||||||
|
// emit on the admin document (the asset resolves without error yet no <link> is
|
||||||
|
// produced — the same is true of com_mokosuitebackup.admin), so we attach the
|
||||||
|
// stylesheet directly to the document, which reliably renders via <jdoc:include
|
||||||
|
// type="styles" />.
|
||||||
|
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbackup');
|
||||||
|
$this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebackup/css/runbackup.css');
|
||||||
?>
|
?>
|
||||||
<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="msb-runbackup">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm bg-body-secondary">
|
||||||
<div class="card-body p-4 pb-3">
|
<div class="card-body p-4 pb-3">
|
||||||
<h1 class="h4 mb-1" id="msb-rb-title">
|
<h1 class="h4 mb-1" id="msb-rb-title">
|
||||||
<span class="icon-archive" aria-hidden="true"></span>
|
<span class="icon-archive" aria-hidden="true"></span>
|
||||||
@@ -152,7 +118,17 @@ $config = [
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div id="msb-rb-phase" class="fw-bold mb-1"></div>
|
<div id="msb-rb-phase" class="fw-bold mb-1"></div>
|
||||||
<div id="msb-rb-status" class="text-muted small mb-0"><?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), ENT_QUOTES, 'UTF-8'); ?></div>
|
<div id="msb-rb-status" class="mb-0"><?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), ENT_QUOTES, 'UTF-8'); ?></div>
|
||||||
|
|
||||||
|
<?php if ($safeReturnUrl !== '') : ?>
|
||||||
|
<!-- Pre-update/uninstall flow: opt in to auto-continue, or get a manual "Continue" button on completion. -->
|
||||||
|
<div class="form-check mt-3" id="msb-rb-autocontinue-wrap">
|
||||||
|
<input class="form-check-input" type="checkbox" id="msb-rb-autocontinue" checked>
|
||||||
|
<label class="form-check-label" for="msb-rb-autocontinue">
|
||||||
|
<?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE'), ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div id="msb-rb-actions" class="d-none mt-3">
|
<div id="msb-rb-actions" class="d-none mt-3">
|
||||||
<button type="button" id="msb-rb-retry" class="btn btn-primary d-none"></button>
|
<button type="button" id="msb-rb-retry" class="btn btn-primary d-none"></button>
|
||||||
@@ -164,7 +140,7 @@ $config = [
|
|||||||
|
|
||||||
<!-- Full-bleed progress bar, edge-to-edge along the bottom of the card. -->
|
<!-- 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 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,7 +164,9 @@ $config = [
|
|||||||
retry: document.getElementById('msb-rb-retry'),
|
retry: document.getElementById('msb-rb-retry'),
|
||||||
record: document.getElementById('msb-rb-record'),
|
record: document.getElementById('msb-rb-record'),
|
||||||
continue: document.getElementById('msb-rb-continue'),
|
continue: document.getElementById('msb-rb-continue'),
|
||||||
dashboard: document.getElementById('msb-rb-dashboard')
|
dashboard: document.getElementById('msb-rb-dashboard'),
|
||||||
|
autocontinue: document.getElementById('msb-rb-autocontinue'),
|
||||||
|
autocontinueWrap: document.getElementById('msb-rb-autocontinue-wrap')
|
||||||
};
|
};
|
||||||
|
|
||||||
function setBar(pct, striped) {
|
function setBar(pct, striped) {
|
||||||
@@ -227,16 +205,37 @@ $config = [
|
|||||||
setBar(100, false);
|
setBar(100, false);
|
||||||
el.title.textContent = L.complete || 'Backup complete';
|
el.title.textContent = L.complete || 'Backup complete';
|
||||||
|
|
||||||
/* Arm the pre-action throttles so a following server-side onExtension
|
/* Set the one-shot skip flag for THIS action so the following
|
||||||
Before(Update|Uninstall) backup is skipped (no duplicate backup).
|
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. */
|
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) {
|
if (CFG.returnUrl) {
|
||||||
/* Pre-update / pre-uninstall flow: continue the original action.
|
/* Pre-update / pre-uninstall flow. If "auto-continue" is ticked
|
||||||
No "view record" button here — we're handing back to Joomla. */
|
(default) hand back to Joomla automatically; otherwise stop here
|
||||||
setStatus(L.continuing || 'Continuing…');
|
and offer a link to the new backup record plus a manual
|
||||||
window.setTimeout(function () { window.location.href = CFG.returnUrl; }, 1200);
|
"Continue the update" button. */
|
||||||
|
if (el.autocontinueWrap) { el.autocontinueWrap.classList.add('d-none'); }
|
||||||
|
|
||||||
|
var autoContinue = !el.autocontinue || el.autocontinue.checked;
|
||||||
|
|
||||||
|
if (autoContinue) {
|
||||||
|
setStatus(L.continuing || 'Continuing…');
|
||||||
|
window.setTimeout(function () { window.location.href = CFG.returnUrl; }, 1200);
|
||||||
|
} else {
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
el.continue.classList.remove('btn-warning');
|
||||||
|
el.continue.classList.add('btn-success');
|
||||||
|
showBtn(el.continue, L.continueUpdate || 'Continue the update', CFG.returnUrl);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Manual "Backup Now": offer to view the record that was just made. */
|
/* Manual "Backup Now": offer to view the record that was just made. */
|
||||||
setStatus(L.complete || 'Backup complete');
|
setStatus(L.complete || 'Backup complete');
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="module" client="administrator" method="upgrade">
|
<extension type="module" client="administrator" method="upgrade">
|
||||||
<name>Module - MokoSuiteBackup - cPanel</name>
|
<name>Module - MokoSuiteBackup - cPanel</name>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-23</creationDate>
|
<creationDate>2026-06-23</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="actionlog" method="upgrade">
|
<extension type="plugin" group="actionlog" method="upgrade">
|
||||||
<name>Action Log - MokoSuiteBackup</name>
|
<name>Action Log - MokoSuiteBackup</name>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-06-04</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="console" method="upgrade">
|
<extension type="plugin" group="console" method="upgrade">
|
||||||
<name>Console - MokoSuiteBackup</name>
|
<name>Console - MokoSuiteBackup</name>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-06-04</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="content" method="upgrade">
|
<extension type="plugin" group="content" method="upgrade">
|
||||||
<name>Content - MokoSuiteBackup</name>
|
<name>Content - MokoSuiteBackup</name>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-06-04</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="quickicon" method="upgrade">
|
<extension type="plugin" group="quickicon" method="upgrade">
|
||||||
<name>Quick Icon - MokoSuiteBackup</name>
|
<name>Quick Icon - MokoSuiteBackup</name>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -73,6 +73,10 @@
|
|||||||
var ret = window.location.href;
|
var ret = window.location.href;
|
||||||
ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1';
|
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
|
/* 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
|
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
|
encode as UTF-8 first; if it still fails, bail WITHOUT preventing the
|
||||||
@@ -81,6 +85,7 @@
|
|||||||
try {
|
try {
|
||||||
target = cfg.runbackupUrl
|
target = cfg.runbackupUrl
|
||||||
+ '&profile_id=' + encodeURIComponent(cfg.profileId)
|
+ '&profile_id=' + encodeURIComponent(cfg.profileId)
|
||||||
|
+ '&msb_action=' + action
|
||||||
+ '&returnurl=' + encodeURIComponent(btoa(unescape(encodeURIComponent(ret))));
|
+ '&returnurl=' + encodeURIComponent(btoa(unescape(encodeURIComponent(ret))));
|
||||||
} catch (x) {
|
} catch (x) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="system" method="upgrade">
|
<extension type="plugin" group="system" method="upgrade">
|
||||||
<name>System - MokoSuiteBackup</name>
|
<name>System - MokoSuiteBackup</name>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
|
|||||||
{
|
{
|
||||||
protected $autoloadLanguage = true;
|
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
|
public static function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@@ -223,23 +229,20 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
|
|||||||
return;
|
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');
|
$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', [
|
$doc->addScriptOptions('plg_system_mokosuitebackup.installer', [
|
||||||
'runbackupUrl' => Route::_('index.php?option=com_mokosuitebackup&view=runbackup&tmpl=component&autostart=1', false),
|
'runbackupUrl' => Route::_('index.php?option=com_mokosuitebackup&view=runbackup&tmpl=component&autostart=1', false),
|
||||||
'profileId' => (int) $params->get('default_profile', 1),
|
'profileId' => (int) $params->get('default_profile', 1),
|
||||||
'backupBeforeUpdate' => $view === 'update' && (bool) $beforeUpdate,
|
'backupBeforeUpdate' => $view === 'update' && (bool) $beforeUpdate,
|
||||||
'backupBeforeUninstall' => $view === 'manage' && (bool) $beforeUninst,
|
'backupBeforeUninstall' => $view === 'manage' && (bool) $beforeUninst,
|
||||||
'recentBackup' => $recent,
|
'recentBackup' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/installer-backup.js', [], ['defer' => true]);
|
$doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/installer-backup.js', [], ['defer' => true]);
|
||||||
@@ -313,16 +316,20 @@ JS;
|
|||||||
|
|
||||||
$session = Factory::getSession();
|
$session = Factory::getSession();
|
||||||
|
|
||||||
// Returned from the backup screen: arm the throttle so the synchronous
|
// Returned from the backup screen: mark this core update backed up so we
|
||||||
// onExtensionBeforeUpdate backup doesn't run again, then let it proceed.
|
// 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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backed up recently already — let the update proceed.
|
// Backed up this core update recently already — let the update proceed
|
||||||
$lastRun = (int) $session->get('mokosuitebackup.preaction_backup_before_update', 0);
|
// without redirecting to the backup screen again.
|
||||||
|
$lastRun = (int) $session->get('mokosuitebackup.core_update_backed_up', 0);
|
||||||
|
|
||||||
if ($lastRun > 0 && (time() - $lastRun) < 600) {
|
if ($lastRun > 0 && (time() - $lastRun) < 600) {
|
||||||
return;
|
return;
|
||||||
@@ -525,17 +532,37 @@ JS;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throttle: only run once per 10 minutes to prevent duplicate
|
/* One backup per Update action — NOT a 10-minute time throttle. The old
|
||||||
// backups when multiple extensions are updated in a batch
|
600s window suppressed every extension's pre-action backup for 10
|
||||||
$session = Factory::getSession();
|
minutes after any single one ran (e.g. after updating this very
|
||||||
$sessionKey = 'mokosuitebackup.preaction_' . $paramName;
|
extension), so distinct updates silently went un-backed-up. Instead:
|
||||||
$lastRun = $session->get($sessionKey, 0);
|
|
||||||
|
|
||||||
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;
|
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);
|
$profileId = (int) $params->get('default_profile', 1);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="task" method="upgrade">
|
<extension type="plugin" group="task" method="upgrade">
|
||||||
<name>Task - MokoSuiteBackup</name>
|
<name>Task - MokoSuiteBackup</name>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="webservices" method="upgrade">
|
<extension type="plugin" group="webservices" method="upgrade">
|
||||||
<name>Web Services - MokoSuiteBackup</name>
|
<name>Web Services - MokoSuiteBackup</name>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<extension type="package" method="upgrade">
|
<extension type="package" method="upgrade">
|
||||||
<name>Package - MokoSuiteBackup</name>
|
<name>Package - MokoSuiteBackup</name>
|
||||||
<packagename>mokosuitebackup</packagename>
|
<packagename>mokosuitebackup</packagename>
|
||||||
<version>02.59.00</version>
|
<version>02.62.05</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
Reference in New Issue
Block a user