From 9bc0fc8ad84e438f2d57a00cbf19baaa60bc7d0a Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 00:33:03 +0000 Subject: [PATCH 01/51] chore(version): pre-release bump to 02.58.01-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.01.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.01.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index f1cc7ac0..d43ab350 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.00 +# VERSION: 02.58.01 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index fdc86d1e..89cce3cd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.00 +VERSION: 02.58.01 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 68607c4d..0dd24808 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.00 + 02.58.01 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.01.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.01.sql new file mode 100644 index 00000000..726a3aa9 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.01.sql @@ -0,0 +1 @@ +/* 02.58.01 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index a4c96c57..1b7dc7c8 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.00 + 02.58.01 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index d98070ea..7a7aed88 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.00 + 02.58.01 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 6e4d4247..38a69608 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.00 + 02.58.01 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index adfd9b2c..5009e4f2 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.00 + 02.58.01 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 47e4bde1..98fc7343 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.00 + 02.58.01 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index ed881e3d..59a923fa 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.00 + 02.58.01 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 8414db5f..97f73050 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.00 + 02.58.01 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 8bf5624a..01853f08 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.00 + 02.58.01 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 3e10a08e..e7983311 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.00 + 02.58.01 2026-06-02 Moko Consulting hello@mokoconsulting.tech From bf91611b976fc97b9e07e1065d733849f7b7a7f4 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 19:39:37 -0500 Subject: [PATCH 02/51] fix(engine): check + log restore-script remote upload (no longer silent) The standalone restore script was uploaded to each remote with the result discarded and nothing logged, so a failed second upload left the restore script missing from the remote with no warning while the archive still recorded 'complete'. Capture the result: log success, and on failure set uploadFailed (-> warning status) and log the reason. Fixes #226 Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 2 ++ .../com_mokosuitebackup/src/Engine/BackupEngine.php | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a461584f..3de1626b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +### Fixed +- Remote upload: the standalone restore script upload is no longer silent — its result is now checked and logged, and a failed restore-script upload marks the backup as `warning` (previously the result was discarded, so a missing restore script on the remote went unreported while the archive still showed success). (#226) ## [02.58.00] --- 2026-07-06 diff --git a/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php b/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php index d9105d32..71f014ab 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php @@ -304,7 +304,18 @@ class BackupEngine $this->log(' Upload complete: ' . $result['message']); if (!empty($restoreScriptPath) && is_file($restoreScriptPath)) { - $uploader->upload($restoreScriptPath, basename($restoreScriptPath)); + $scriptName = basename($restoreScriptPath); + $this->log(' Uploading restore script: ' . $scriptName . '...'); + $scriptResult = $uploader->upload($restoreScriptPath, $scriptName); + + if (!empty($scriptResult['success'])) { + $this->log(' Restore script uploaded: ' . ($scriptResult['message'] ?? $scriptName)); + } else { + /* Never silent: a missing restore script makes the archive + much harder to restore, so surface it as a warning. */ + $uploadFailed = true; + $this->log(' WARNING: Restore script upload failed: ' . ($scriptResult['message'] ?? 'unknown error')); + } } } else { $uploadFailed = true; From 35febb88549ffd843177deeda33ca26ea24dda1d Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 00:39:57 +0000 Subject: [PATCH 03/51] chore(version): pre-release bump to 02.58.02-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.02.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.02.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index d43ab350..eb07565a 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.01 +# VERSION: 02.58.02 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 89cce3cd..c6da2011 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.01 +VERSION: 02.58.02 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 0dd24808..ddfbebae 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.01 + 02.58.02 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.02.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.02.sql new file mode 100644 index 00000000..56b3dc21 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.02.sql @@ -0,0 +1 @@ +/* 02.58.02 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 1b7dc7c8..e6862e96 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.01 + 02.58.02 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 7a7aed88..c5806171 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.01 + 02.58.02 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 38a69608..70e2fab5 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.01 + 02.58.02 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 5009e4f2..2130f2d2 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.01 + 02.58.02 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 98fc7343..d79a5fee 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.01 + 02.58.02 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 59a923fa..21d3fdaa 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.01 + 02.58.02 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 97f73050..146b8b3d 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.01 + 02.58.02 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 01853f08..59e66f99 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.01 + 02.58.02 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index e7983311..43776c3a 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.01 + 02.58.02 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 1c0a87ca4a90c08a6067cc4f40d86a704fd7964b Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 00:40:33 +0000 Subject: [PATCH 04/51] chore(version): pre-release bump to 02.58.03-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.03.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.03.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index eb07565a..6345280a 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.02 +# VERSION: 02.58.03 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index c6da2011..2bbb541c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.02 +VERSION: 02.58.03 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index ddfbebae..671b104c 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.02 + 02.58.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.03.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.03.sql new file mode 100644 index 00000000..1dd02ee1 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.03.sql @@ -0,0 +1 @@ +/* 02.58.03 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index e6862e96..cec57d03 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.02 + 02.58.03 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index c5806171..ae30f57f 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.02 + 02.58.03 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 70e2fab5..f4eef3e0 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.02 + 02.58.03 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 2130f2d2..2b84e8f5 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.02 + 02.58.03 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index d79a5fee..158846e2 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.02 + 02.58.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 21d3fdaa..7f4fa937 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.02 + 02.58.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 146b8b3d..ab791f1a 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.02 + 02.58.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 59e66f99..99f09d72 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.02 + 02.58.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 43776c3a..45a50d16 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.02 + 02.58.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 0ae9bfd93a130d29880f3765b963108482092bf4 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 20:15:30 -0500 Subject: [PATCH 05/51] fix: restore missing subject arg in MokoRestore str_replace (standalone gen) MokoRestore::generateStandaloneScript() rewrites the 'Backup Archive' pre-check via str_replace(ORIG, REPL) but was missing the third $php subject argument, so every standalone-mode backup fatally errored with 'str_replace() expects at least 3 arguments, 2 given' at the 'Generating standalone restore.php...' step. The archive still finalized and uploaded, but no restore.php was produced -- the underlying cause of #226 (restore script missing on the remote). Refs #226 Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 1 + source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3de1626b..4d684f15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Fixed +- Standalone restore script generation no longer aborts backups with `str_replace() expects at least 3 arguments, 2 given`. `MokoRestore::generateStandaloneScript()` had a `str_replace()` call (the "Backup Archive" pre-check rewrite) that was missing its `$php` subject argument, so **every** standalone-mode backup fatally errored while "Generating standalone restore.php…" — the archive still finalized and uploaded, but no `restore.php` was ever produced (the true root cause behind #226). (#226) - Remote upload: the standalone restore script upload is no longer silent — its result is now checked and logged, and a failed restore-script upload marks the backup as `warning` (previously the result was discarded, so a missing restore script on the remote went unreported while the archive still showed success). (#226) ## [02.58.00] --- 2026-07-06 diff --git a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php index eb6ea0aa..1d06801b 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php +++ b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php @@ -204,7 +204,8 @@ ORIG, 'ok' => $backupCount > 0, 'hint' => 'Place one or more backup ZIP files in the same directory as ' . basename($_SERVER['SCRIPT_NAME']), ]; -REPL +REPL, + $php ); /* Modify remaining pre-checks to use getSelectedBackupFile() */ From 0a51ed75da09675c08f8b5f01a37d9af53ddb317 Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 01:16:02 +0000 Subject: [PATCH 06/51] chore(version): pre-release bump to 02.58.04-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.04.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.04.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 6345280a..bee798d3 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.03 +# VERSION: 02.58.04 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 2bbb541c..d60ba8bc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.03 +VERSION: 02.58.04 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 671b104c..e86edcaf 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.03 + 02.58.04 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.04.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.04.sql new file mode 100644 index 00000000..6add05b7 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.04.sql @@ -0,0 +1 @@ +/* 02.58.04 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index cec57d03..07c141eb 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.03 + 02.58.04 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index ae30f57f..04bf09cd 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.03 + 02.58.04 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index f4eef3e0..8ae56e4e 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.03 + 02.58.04 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 2b84e8f5..2e9dbb1c 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.03 + 02.58.04 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 158846e2..2ae83e8e 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.03 + 02.58.04 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 7f4fa937..fb378331 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.03 + 02.58.04 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index ab791f1a..a600aa26 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.03 + 02.58.04 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 99f09d72..9c18f71a 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.03 + 02.58.04 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 45a50d16..8245bc34 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.03 + 02.58.04 2026-06-02 Moko Consulting hello@mokoconsulting.tech From f56c1bfbd18615c15184d0cc8b1c8391d6d0985f Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 01:17:22 +0000 Subject: [PATCH 07/51] chore(version): pre-release bump to 02.58.05-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.05.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.05.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index bee798d3..6f668382 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.04 +# VERSION: 02.58.05 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index d60ba8bc..7ef06949 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.04 +VERSION: 02.58.05 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index e86edcaf..25d32596 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.04 + 02.58.05 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.05.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.05.sql new file mode 100644 index 00000000..228fb8d0 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.05.sql @@ -0,0 +1 @@ +/* 02.58.05 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 07c141eb..1aef54bd 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.04 + 02.58.05 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 04bf09cd..780f2636 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.04 + 02.58.05 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 8ae56e4e..72c21305 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.04 + 02.58.05 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 2e9dbb1c..ad424664 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.04 + 02.58.05 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 2ae83e8e..a77ab126 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.04 + 02.58.05 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index fb378331..fb0a7228 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.04 + 02.58.05 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index a600aa26..1b0f7b99 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.04 + 02.58.05 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 9c18f71a..3ad25bac 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.04 + 02.58.05 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 8245bc34..1d37622c 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.04 + 02.58.05 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 447f7b572e71362f976b164f49bc86b049e02d7d Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 20:34:04 -0500 Subject: [PATCH 08/51] feat: prune remote backups on retention + consolidate backup helpers Retention (RetentionManager and the plugin's hourly cleanup) only ever deleted local files; remote archives grew unbounded. Add an idempotent delete() to RemoteUploaderInterface + all four uploaders and have RetentionManager remove each pruned archive from the profile's enabled remotes (best-effort; failures logged). The shared restore.php is left in place since every backup overwrites it. (#229) Consolidate duplicated plumbing (#230): - New RemoteUploaderFactory replaces the createUploaderFromParams copy duplicated in BackupEngine and SteppedBackupEngine. - RetentionManager becomes the single retention authority (global-default fallback + pruneOrphans()); the system plugin delegates to it and its duplicate doCleanup()/deleteBackupRecord() logic is removed. - Backend controller, API controller and legacy cli/mokosuitebackup.php now run through the shared BackupRunner instead of BackupEngine directly. Refs #229 #230 Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 9 + .../api/src/Controller/BackupsController.php | 5 +- .../cli/mokosuitebackup.php | 5 +- .../src/Controller/BackupsController.php | 5 +- .../src/Engine/BackupEngine.php | 19 +-- .../src/Engine/FtpUploader.php | 46 +++++ .../src/Engine/GoogleDriveUploader.php | 53 ++++++ .../src/Engine/RemoteUploaderFactory.php | 69 ++++++++ .../src/Engine/RemoteUploaderInterface.php | 15 ++ .../src/Engine/RetentionManager.php | 160 ++++++++++++++++-- .../src/Engine/S3Uploader.php | 48 ++++++ .../src/Engine/SftpUploader.php | 34 ++++ .../src/Engine/SteppedBackupEngine.php | 19 +-- .../src/Extension/MokoSuiteBackup.php | 107 ++---------- 14 files changed, 440 insertions(+), 154 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/src/Engine/RemoteUploaderFactory.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d684f15..cb620142 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## [Unreleased] +### Added +- Retention now prunes **remote** copies too: when a backup is pruned by age/count, its archive is deleted from every enabled remote destination (SFTP / FTP / S3 / Google Drive), not just the local copy. Each uploader gained an idempotent `delete()` method (already-absent file = success), and removal is best-effort — a failing destination is logged but never blocks local pruning. The shared standalone `restore.php` is intentionally left in place (every backup overwrites it, so newer backups still depend on it). (#229) + +### Changed +- Consolidated backup plumbing into shared helpers (#230): + - New `RemoteUploaderFactory` replaces the `createUploaderFromParams()` copy that was duplicated in `BackupEngine` and `SteppedBackupEngine`. + - `RetentionManager` is now the single retention authority — it takes the global `max_age_days`/`max_backups` fallback and gained `pruneOrphans()`; the system plugin's hourly cleanup delegates to it and its duplicate `deleteBackupRecord()` logic is removed. + - The backend controller, Web Services API controller, and legacy `cli/mokosuitebackup.php` now run backups through the shared `BackupRunner` (gaining the normalized complete/warning/fail status) instead of instantiating `BackupEngine` directly. + ### Fixed - Standalone restore script generation no longer aborts backups with `str_replace() expects at least 3 arguments, 2 given`. `MokoRestore::generateStandaloneScript()` had a `str_replace()` call (the "Backup Archive" pre-check rewrite) that was missing its `$php` subject argument, so **every** standalone-mode backup fatally errored while "Generating standalone restore.php…" — the archive still finalized and uploaded, but no `restore.php` was ever produced (the true root cause behind #226). (#226) - Remote upload: the standalone restore script upload is no longer silent — its result is now checked and logged, and a failed restore-script upload marks the backup as `warning` (previously the result was discarded, so a missing restore script on the remote went unreported while the archive still showed success). (#226) diff --git a/source/packages/com_mokosuitebackup/api/src/Controller/BackupsController.php b/source/packages/com_mokosuitebackup/api/src/Controller/BackupsController.php index 2cc5ebe5..5bce4504 100644 --- a/source/packages/com_mokosuitebackup/api/src/Controller/BackupsController.php +++ b/source/packages/com_mokosuitebackup/api/src/Controller/BackupsController.php @@ -13,7 +13,7 @@ namespace Joomla\Component\MokoSuiteBackup\Api\Controller; defined('_JEXEC') or die; use Joomla\CMS\MVC\Controller\ApiController; -use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine; +use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner; class BackupsController extends ApiController { @@ -38,8 +38,7 @@ class BackupsController extends ApiController $profileId = (int) ($data['profile'] ?? 1); $description = $data['description'] ?? 'API backup ' . date('Y-m-d H:i:s'); - $engine = new BackupEngine(); - $result = $engine->run($profileId, $description, 'api'); + $result = (new BackupRunner())->run($profileId, $description, 'api'); if ($result['success']) { $this->app->setHeader('status', 200); diff --git a/source/packages/com_mokosuitebackup/cli/mokosuitebackup.php b/source/packages/com_mokosuitebackup/cli/mokosuitebackup.php index 29f1c936..7e78076f 100644 --- a/source/packages/com_mokosuitebackup/cli/mokosuitebackup.php +++ b/source/packages/com_mokosuitebackup/cli/mokosuitebackup.php @@ -30,7 +30,7 @@ if (!defined('JPATH_BASE')) { require_once JPATH_BASE . '/includes/framework.php'; use Joomla\CMS\Factory; -use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine; +use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner; // Parse CLI arguments $profileId = 1; @@ -56,8 +56,7 @@ echo "Profile: {$profileId}\n"; echo "Description: {$description}\n"; echo "Starting backup...\n\n"; -$engine = new BackupEngine(); -$result = $engine->run($profileId, $description, 'cli'); +$result = (new BackupRunner())->run($profileId, $description, 'cli'); if ($result['success']) { echo "SUCCESS: " . $result['message'] . "\n"; diff --git a/source/packages/com_mokosuitebackup/src/Controller/BackupsController.php b/source/packages/com_mokosuitebackup/src/Controller/BackupsController.php index 59a725b5..31086528 100644 --- a/source/packages/com_mokosuitebackup/src/Controller/BackupsController.php +++ b/source/packages/com_mokosuitebackup/src/Controller/BackupsController.php @@ -16,8 +16,8 @@ use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\AdminController; use Joomla\CMS\Router\Route; use Joomla\CMS\Session\Session; -use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RestoreEngine; +use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner; class BackupsController extends AdminController { @@ -54,8 +54,7 @@ class BackupsController extends AdminController $profileId = $this->input->getInt('profile_id', 1); $description = $this->input->getString('description', ''); - $engine = new BackupEngine(); - $result = $engine->run($profileId, $description, 'backend'); + $result = (new BackupRunner())->run($profileId, $description, 'backend'); // Surface preflight warnings as Joomla messages if (!empty($result['warnings'])) { diff --git a/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php b/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php index 71f014ab..5fd38bfa 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php @@ -511,24 +511,7 @@ class BackupEngine */ private function createUploaderFromParams(string $type, array $params): RemoteUploaderInterface { - $prefixMap = ['ftp' => 'ftp_', 'sftp' => 'sftp_', 's3' => 's3_', 'google_drive' => 'gdrive_']; - $prefix = $prefixMap[$type] ?? ''; - - $prefixed = []; - - foreach ($params as $key => $value) { - $prefixed[$prefix . $key] = $value; - } - - $fake = (object) $prefixed; - - return match ($type) { - 'ftp' => new FtpUploader($fake), - 'sftp' => new SftpUploader($fake), - 'google_drive' => new GoogleDriveUploader($fake), - 's3' => new S3Uploader($fake), - default => throw new \InvalidArgumentException('Unknown remote storage type: ' . $type), - }; + return RemoteUploaderFactory::create($type, $params); } /** diff --git a/source/packages/com_mokosuitebackup/src/Engine/FtpUploader.php b/source/packages/com_mokosuitebackup/src/Engine/FtpUploader.php index 465f0340..265c399c 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/FtpUploader.php +++ b/source/packages/com_mokosuitebackup/src/Engine/FtpUploader.php @@ -89,6 +89,52 @@ class FtpUploader implements RemoteUploaderInterface } } + public function delete(string $remoteName): array + { + if (!extension_loaded('ftp')) { + return ['success' => false, 'message' => 'PHP ext-ftp is required for FTP deletes. Enable it in php.ini.']; + } + + if (empty($this->host)) { + return ['success' => false, 'message' => 'FTP host is not configured']; + } + + $conn = $this->connect(); + + if (!$conn) { + return ['success' => false, 'message' => 'Failed to connect to FTP server ' . $this->host . ':' . $this->port]; + } + + try { + if (!@ftp_login($conn, $this->username, $this->password)) { + throw new \RuntimeException('FTP login failed for user: ' . $this->username); + } + + if ($this->passive) { + ftp_pasv($conn, true); + } + + $remoteFile = $this->remotePath . '/' . $remoteName; + + /* Already-absent files: ftp_delete returns false, but if the file + isn't there the retention goal is already met, so probe size to + decide. ftp_size returns -1 when the file does not exist. */ + if (@ftp_size($conn, $remoteFile) < 0) { + return ['success' => true, 'message' => 'FTP: nothing to delete (' . $remoteFile . ')']; + } + + if (!@ftp_delete($conn, $remoteFile)) { + throw new \RuntimeException('ftp_delete failed for: ' . $remoteFile); + } + + return ['success' => true, 'message' => 'Deleted from FTP: ' . $remoteFile]; + } catch (\Throwable $e) { + return ['success' => false, 'message' => 'FTP delete failed: ' . $e->getMessage()]; + } finally { + @ftp_close($conn); + } + } + public function testConnection(): array { if (empty($this->host)) { diff --git a/source/packages/com_mokosuitebackup/src/Engine/GoogleDriveUploader.php b/source/packages/com_mokosuitebackup/src/Engine/GoogleDriveUploader.php index 7d1d83d9..c57d1478 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/GoogleDriveUploader.php +++ b/source/packages/com_mokosuitebackup/src/Engine/GoogleDriveUploader.php @@ -81,6 +81,59 @@ class GoogleDriveUploader implements RemoteUploaderInterface } } + public function delete(string $remoteName): array + { + if (!extension_loaded('curl')) { + return ['success' => false, 'message' => 'PHP ext-curl is required for Google Drive deletes. Enable it in php.ini.']; + } + + if (empty($this->clientId) || empty($this->refreshToken)) { + return ['success' => false, 'message' => 'Google Drive credentials not configured']; + } + + try { + $accessToken = $this->getAccessToken(); + + /* Drive has no path addressing — resolve the file id(s) by name + within the configured folder, then delete each match. */ + $q = "name = '" . str_replace("'", "\\'", $remoteName) . "' and trashed = false"; + + if (!empty($this->folderId)) { + $q .= " and '" . $this->folderId . "' in parents"; + } + + $listUrl = self::API_URL . '/files?q=' . rawurlencode($q) . '&fields=' . rawurlencode('files(id,name)') . '&pageSize=25'; + $response = $this->curlRequest('GET', $listUrl, null, [ + 'Authorization: Bearer ' . $accessToken, + ]); + + if ($response['code'] !== 200) { + throw new \RuntimeException('Drive file lookup failed (HTTP ' . $response['code'] . ')'); + } + + $files = json_decode($response['body'], true)['files'] ?? []; + + if (empty($files)) { + return ['success' => true, 'message' => 'Google Drive: nothing to delete (' . $remoteName . ')']; + } + + foreach ($files as $file) { + $del = $this->curlRequest('DELETE', self::API_URL . '/files/' . rawurlencode($file['id']), null, [ + 'Authorization: Bearer ' . $accessToken, + ]); + + /* 204 = deleted, 404 = already gone — both acceptable. */ + if ($del['code'] !== 204 && $del['code'] !== 200 && $del['code'] !== 404) { + return ['success' => false, 'message' => 'Google Drive delete failed for ' . $remoteName . ' (HTTP ' . $del['code'] . ')']; + } + } + + return ['success' => true, 'message' => 'Deleted from Google Drive: ' . $remoteName]; + } catch (\Throwable $e) { + return ['success' => false, 'message' => 'Google Drive delete failed: ' . $e->getMessage()]; + } + } + public function testConnection(): array { if (empty($this->clientId) || empty($this->refreshToken)) { diff --git a/source/packages/com_mokosuitebackup/src/Engine/RemoteUploaderFactory.php b/source/packages/com_mokosuitebackup/src/Engine/RemoteUploaderFactory.php new file mode 100644 index 00000000..84e39c71 --- /dev/null +++ b/source/packages/com_mokosuitebackup/src/Engine/RemoteUploaderFactory.php @@ -0,0 +1,69 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + */ + +namespace Joomla\Component\MokoSuiteBackup\Administrator\Engine; + +defined('_JEXEC') or die; + +/** + * Builds a remote uploader from a `#__mokosuitebackup_remotes` row. + * + * This was previously a private method duplicated in both BackupEngine and + * SteppedBackupEngine (and needed a third time by RetentionManager for remote + * deletion). It is centralised here so the type→class map and the param + * key-prefixing convention live in exactly one place. + * + * The remotes table stores per-type settings as a flat JSON object with + * un-prefixed keys (e.g. `{"host":"…","path":"/backups"}`). The concrete + * uploaders, however, read prefixed keys off a profile-shaped object + * (e.g. `sftp_host`, `s3_bucket`). This factory bridges the two. + */ +final class RemoteUploaderFactory +{ + /** + * Map of remote type → the key prefix its uploader expects. + */ + private const PREFIX_MAP = [ + 'ftp' => 'ftp_', + 'sftp' => 'sftp_', + 's3' => 's3_', + 'google_drive' => 'gdrive_', + ]; + + /** + * Create an uploader for the given remote type from decoded JSON params. + * + * @param string $type Remote type: ftp, sftp, s3, google_drive + * @param array $params Key-value params decoded from the remote's JSON + * + * @return RemoteUploaderInterface + * + * @throws \InvalidArgumentException On an unknown remote type + */ + public static function create(string $type, array $params): RemoteUploaderInterface + { + $prefix = self::PREFIX_MAP[$type] ?? ''; + $prefixed = []; + + foreach ($params as $key => $value) { + $prefixed[$prefix . $key] = $value; + } + + $fake = (object) $prefixed; + + return match ($type) { + 'ftp' => new FtpUploader($fake), + 'sftp' => new SftpUploader($fake), + 'google_drive' => new GoogleDriveUploader($fake), + 's3' => new S3Uploader($fake), + default => throw new \InvalidArgumentException('Unknown remote storage type: ' . $type), + }; + } +} diff --git a/source/packages/com_mokosuitebackup/src/Engine/RemoteUploaderInterface.php b/source/packages/com_mokosuitebackup/src/Engine/RemoteUploaderInterface.php index fdc67a55..9b64956e 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/RemoteUploaderInterface.php +++ b/source/packages/com_mokosuitebackup/src/Engine/RemoteUploaderInterface.php @@ -24,6 +24,21 @@ interface RemoteUploaderInterface */ public function upload(string $localPath, string $remoteName): array; + /** + * Delete a previously-uploaded file from remote storage. + * + * Used by retention pruning so that expiring a local backup also removes + * its archive from every remote destination. Implementations should treat + * an already-absent remote file as success (idempotent delete), so a + * partially-cleaned destination never blocks pruning. + * + * @param string $remoteName Filename on the remote end (the same + * $remoteName that was passed to upload()) + * + * @return array{success: bool, message: string} + */ + public function delete(string $remoteName): array; + /** * Test the connection / credentials without uploading. * diff --git a/source/packages/com_mokosuitebackup/src/Engine/RetentionManager.php b/source/packages/com_mokosuitebackup/src/Engine/RetentionManager.php index 8a611ba9..d3f1ee2a 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/RetentionManager.php +++ b/source/packages/com_mokosuitebackup/src/Engine/RetentionManager.php @@ -15,32 +15,54 @@ defined('_JEXEC') or die; use Joomla\Component\MokoSuiteBackup\Administrator\Utility\BackupDirectory; /** - * Enforces per-profile backup retention. + * Enforces per-profile backup retention — the single retention authority. * * A profile may cap retained backups by age (retention_days) and/or by - * number of copies (retention_count). A backup is pruned when EITHER rule - * matches: it is older than retention_days OR it falls outside the newest - * retention_count copies. Deleting a record also removes its archive and - * log file, mirroring the Backup table's delete(). + * number of copies (retention_count). When a profile leaves a rule at 0 the + * caller-supplied global default is used instead. A backup is pruned when + * EITHER rule matches: it is older than the effective days OR it falls outside + * the newest effective count of copies. + * + * Pruning a record removes, in this order: + * 1. the DB row (#__mokosuitebackup_records), + * 2. the local archive + its .log file, and + * 3. the archive copy on every enabled remote destination for the profile. + * + * The standalone restore script (restore.php) is intentionally NOT deleted + * from remotes: it is a single, fixed-name file that every backup overwrites, + * so newer backups still depend on it — removing it while pruning an old + * archive would break restore for the copies that remain. + * + * Because records do not store which destinations they were uploaded to, + * remote deletion targets the profile's *currently enabled* remotes. Remote + * deletes are idempotent (an already-absent file counts as success) and + * best-effort: a failing remote is logged but never blocks local pruning. */ final class RetentionManager { /** * Prune old backups for a profile according to its retention settings. * - * Called after a backup completes. Only 'complete' and 'warning' records - * are considered — pending/running/failed records are never pruned here. + * Called after a backup completes and from the periodic admin-side cleanup. + * Only 'complete' and 'warning' records are considered — pending/running/ + * failed records are never pruned here. * - * @param object $db Database driver - * @param object $profile Profile row (needs id, retention_days, retention_count) + * @param object $db Database driver + * @param object $profile Profile row (needs id, retention_days, retention_count) + * @param int $globalDays Fallback max age (days) when the profile's is 0 + * @param int $globalCount Fallback max copies when the profile's is 0 * * @return int Number of backup records deleted */ - public static function prune(object $db, object $profile): int + public static function prune(object $db, object $profile, int $globalDays = 0, int $globalCount = 0): int { $days = (int) ($profile->retention_days ?? 0); $count = (int) ($profile->retention_count ?? 0); + // A profile value of 0 means "use the global default". + $days = $days > 0 ? $days : $globalDays; + $count = $count > 0 ? $count : $globalCount; + // No retention configured — nothing to do. if ($days <= 0 && $count <= 0) { return 0; @@ -48,7 +70,7 @@ final class RetentionManager // Newest first, so the index is the copy's position from the top. $query = $db->getQuery(true) - ->select($db->quoteName(['id', 'absolute_path', 'backupstart'])) + ->select($db->quoteName(['id', 'archivename', 'absolute_path', 'backupstart'])) ->from($db->quoteName('#__mokosuitebackup_records')) ->where($db->quoteName('profile_id') . ' = ' . (int) $profile->id) ->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning'])) . ')') @@ -61,6 +83,7 @@ final class RetentionManager } $cutoffTs = $days > 0 ? (time() - ($days * 86400)) : null; + $remotes = self::loadEnabledRemotes($db, (int) $profile->id); $deleted = 0; foreach ($records as $index => $record) { @@ -72,7 +95,7 @@ final class RetentionManager continue; } - if (self::deleteRecord($db, $record)) { + if (self::deleteRecord($db, $record, $remotes)) { $deleted++; } } @@ -81,12 +104,54 @@ final class RetentionManager } /** - * Delete a single backup record and its on-disk archive + log file. + * Delete backup records whose profile no longer exists. * - * The DB row is removed first; the files are only unlinked if that - * succeeds, so a failed delete never orphans the record from its files. + * Orphans have no owning profile and therefore no remote destinations to + * reconcile, so only the local archive/log and DB row are removed. + * + * @param object $db Database driver + * + * @return int Number of orphaned records deleted */ - private static function deleteRecord(object $db, object $record): bool + public static function pruneOrphans(object $db): int + { + $query = $db->getQuery(true) + ->select($db->quoteName(['r.id', 'r.archivename', 'r.absolute_path'], ['id', 'archivename', 'absolute_path'])) + ->from($db->quoteName('#__mokosuitebackup_records', 'r')) + ->join( + 'LEFT', + $db->quoteName('#__mokosuitebackup_profiles', 'p') + . ' ON ' . $db->quoteName('p.id') . ' = ' . $db->quoteName('r.profile_id') + ) + ->where($db->quoteName('p.id') . ' IS NULL') + ->where($db->quoteName('r.status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning'])) . ')'); + $db->setQuery($query); + $orphans = $db->loadObjectList() ?: []; + + $deleted = 0; + + foreach ($orphans as $record) { + if (self::deleteRecord($db, $record, [])) { + $deleted++; + } + } + + return $deleted; + } + + /** + * Delete a single backup record, its on-disk archive + log file, and the + * archive copy on each enabled remote destination. + * + * The DB row is removed first; the local files are only unlinked if that + * succeeds, so a failed delete never orphans the record from its files. + * Remote deletion is best-effort and runs regardless. + * + * @param object $db Database driver + * @param object $record Record row (needs id, archivename, absolute_path) + * @param object[] $remotes Enabled remote rows (type, params) for the profile + */ + private static function deleteRecord(object $db, object $record, array $remotes): bool { $query = $db->getQuery(true) ->delete($db->quoteName('#__mokosuitebackup_records')) @@ -113,6 +178,69 @@ final class RetentionManager } } + self::deleteFromRemotes($record, $remotes); + return true; } + + /** + * Remove the record's archive from every enabled remote destination. + * + * Best-effort: each remote is tried independently and failures are logged, + * never thrown, so one unreachable destination can't stall retention. + * + * @param object $record Record row (needs archivename) + * @param object[] $remotes Enabled remote rows (type, params) + */ + private static function deleteFromRemotes(object $record, array $remotes): void + { + $archiveName = (string) ($record->archivename ?? ''); + + if ($archiveName === '' || empty($remotes)) { + return; + } + + foreach ($remotes as $remote) { + try { + $params = json_decode((string) ($remote->params ?? ''), true) ?: []; + $uploader = RemoteUploaderFactory::create((string) $remote->type, $params); + $result = $uploader->delete($archiveName); + + if (empty($result['success'])) { + error_log( + 'MokoSuiteBackup: retention could not delete ' . $archiveName + . ' from ' . $remote->type . ' remote: ' . ($result['message'] ?? 'unknown error') + ); + } + } catch (\Throwable $e) { + error_log( + 'MokoSuiteBackup: retention remote-delete error for ' . $archiveName + . ' (' . ($remote->type ?? '?') . '): ' . $e->getMessage() + ); + } + } + } + + /** + * Load the profile's enabled remote destinations (type + params only). + * + * Returns an empty array if the remotes table is missing (very old installs) + * or the profile has none, so callers can iterate unconditionally. + */ + private static function loadEnabledRemotes(object $db, int $profileId): array + { + try { + $query = $db->getQuery(true) + ->select($db->quoteName(['type', 'params'])) + ->from($db->quoteName('#__mokosuitebackup_remotes')) + ->where($db->quoteName('profile_id') . ' = ' . $profileId) + ->where($db->quoteName('enabled') . ' = 1') + ->order($db->quoteName('ordering') . ' ASC'); + $db->setQuery($query); + + return $db->loadObjectList() ?: []; + } catch (\Throwable $e) { + return []; + } + } } diff --git a/source/packages/com_mokosuitebackup/src/Engine/S3Uploader.php b/source/packages/com_mokosuitebackup/src/Engine/S3Uploader.php index 5c70a2c5..cea187e7 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/S3Uploader.php +++ b/source/packages/com_mokosuitebackup/src/Engine/S3Uploader.php @@ -75,6 +75,54 @@ class S3Uploader implements RemoteUploaderInterface } } + public function delete(string $remoteName): array + { + if (!extension_loaded('curl')) { + return ['success' => false, 'message' => 'PHP ext-curl is required for S3 deletes. Enable it in php.ini.']; + } + + if (empty($this->accessKey) || empty($this->secretKey) || empty($this->bucket)) { + return ['success' => false, 'message' => 'S3 credentials or bucket not configured']; + } + + try { + $objectKey = ($this->path ? $this->path . '/' : '') . $remoteName; + $url = $this->getObjectUrl($objectKey); + $headers = $this->signRequest('DELETE', $url, hash('sha256', '')); + + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_CUSTOMREQUEST => 'DELETE', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => 60, + ]); + + $response = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if (curl_errno($ch)) { + $error = curl_error($ch); + curl_close($ch); + + throw new \RuntimeException('cURL error: ' . $error); + } + + curl_close($ch); + + /* S3 DELETE returns 204 on success and — by design — also 204 when + the key never existed. 404 is treated as already-gone too. */ + if ($code === 204 || $code === 200 || $code === 404) { + return ['success' => true, 'message' => 'Deleted from S3: s3://' . $this->bucket . '/' . $objectKey]; + } + + return ['success' => false, 'message' => 'S3 DELETE failed (HTTP ' . $code . '): ' . substr((string) $response, 0, 300)]; + } catch (\Throwable $e) { + return ['success' => false, 'message' => 'S3 delete failed: ' . $e->getMessage()]; + } + } + public function testConnection(): array { if (empty($this->accessKey) || empty($this->secretKey) || empty($this->bucket)) { diff --git a/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php b/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php index d32f4c64..f72e8960 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php +++ b/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php @@ -100,6 +100,40 @@ class SftpUploader implements RemoteUploaderInterface } } + public function delete(string $remoteName): array + { + if (empty($this->host) || empty($this->username)) { + return ['success' => false, 'message' => 'SFTP is not configured']; + } + + $keyFile = null; + + try { + if (!empty($this->keyData)) { + $keyFile = $this->writeTempKey(); + } + + /* rm -f exits 0 even when the file is already gone, so an + already-deleted remote archive is treated as success. */ + $remoteFile = $this->remotePath . '/' . $remoteName; + $cmd = $this->buildSshCommand('rm -f ' . escapeshellarg($remoteFile), $keyFile); + + $output = []; + $exitCode = 0; + exec($cmd . ' 2>&1', $output, $exitCode); + + if ($exitCode !== 0) { + return ['success' => false, 'message' => 'SFTP delete failed: ' . implode(' ', $output)]; + } + + return ['success' => true, 'message' => 'Deleted from SFTP: ' . $remoteFile]; + } catch (\Throwable $e) { + return ['success' => false, 'message' => 'SFTP delete failed: ' . $e->getMessage()]; + } finally { + $this->cleanupTempKey($keyFile); + } + } + public function testConnection(): array { if (empty($this->host)) { diff --git a/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php b/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php index bbcf9069..811a8464 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php @@ -806,24 +806,7 @@ class SteppedBackupEngine */ private function createUploaderFromParams(string $type, array $params): RemoteUploaderInterface { - $prefixMap = ['ftp' => 'ftp_', 'sftp' => 'sftp_', 's3' => 's3_', 'google_drive' => 'gdrive_']; - $prefix = $prefixMap[$type] ?? ''; - - $prefixed = []; - - foreach ($params as $key => $value) { - $prefixed[$prefix . $key] = $value; - } - - $fake = (object) $prefixed; - - return match ($type) { - 'ftp' => new FtpUploader($fake), - 'sftp' => new SftpUploader($fake), - 'google_drive' => new GoogleDriveUploader($fake), - 's3' => new S3Uploader($fake), - default => throw new \InvalidArgumentException('Unknown remote storage type: ' . $type), - }; + return RemoteUploaderFactory::create($type, $params); } } diff --git a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php index 1a97838f..6ec0c2d6 100644 --- a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php +++ b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php @@ -15,6 +15,7 @@ defined('_JEXEC') or die; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\CMSPlugin; +use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RetentionManager; use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner; use Joomla\Event\Event; use Joomla\Event\SubscriberInterface; @@ -241,108 +242,28 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface private function doCleanup(): void { - $db = Factory::getDbo(); - $globalMaxAge = (int) ComponentHelper::getParams('com_mokosuitebackup')->get('max_age_days', 30); - $globalMaxCount = (int) ComponentHelper::getParams('com_mokosuitebackup')->get('max_backups', 10); + $db = Factory::getDbo(); + $params = ComponentHelper::getParams('com_mokosuitebackup'); + $globalMaxAge = (int) $params->get('max_age_days', 30); + $globalMaxCount = (int) $params->get('max_backups', 10); - // Load all published profiles with their retention settings + // Load all published profiles with their retention settings. $query = $db->getQuery(true) - ->select([$db->quoteName('id'), $db->quoteName('retention_days'), $db->quoteName('retention_count')]) + ->select($db->quoteName(['id', 'retention_days', 'retention_count'])) ->from($db->quoteName('#__mokosuitebackup_profiles')) ->where($db->quoteName('published') . ' = 1'); $db->setQuery($query); - $profiles = $db->loadObjectList(); + $profiles = $db->loadObjectList() ?: []; + // Delegate to the single retention authority. RetentionManager prunes by + // age OR count (with these globals as the per-profile fallback) and also + // removes each pruned archive from the profile's remote destinations. foreach ($profiles as $profile) { - $maxAge = (int) $profile->retention_days > 0 ? (int) $profile->retention_days : $globalMaxAge; - $maxCount = (int) $profile->retention_count > 0 ? (int) $profile->retention_count : $globalMaxCount; - $pid = (int) $profile->id; - - // Delete by age for this profile - $cutoff = date('Y-m-d H:i:s', strtotime("-{$maxAge} days")); - $query = $db->getQuery(true) - ->select('id, absolute_path') - ->from($db->quoteName('#__mokosuitebackup_records')) - ->where($db->quoteName('profile_id') . ' = ' . $pid) - ->where($db->quoteName('backupstart') . ' < ' . $db->quote($cutoff)) - ->where($db->quoteName('status') . ' = ' . $db->quote('complete')); - $db->setQuery($query); - $expired = $db->loadObjectList(); - - foreach ($expired as $record) { - $this->deleteBackupRecord($db, $record); - } - - // Enforce max count for this profile (keep newest) - $query = $db->getQuery(true) - ->select('COUNT(*)') - ->from($db->quoteName('#__mokosuitebackup_records')) - ->where($db->quoteName('profile_id') . ' = ' . $pid) - ->where($db->quoteName('status') . ' = ' . $db->quote('complete')); - $db->setQuery($query); - $totalCount = (int) $db->loadResult(); - - if ($totalCount > $maxCount) { - $excess = $totalCount - $maxCount; - $query = $db->getQuery(true) - ->select('id, absolute_path') - ->from($db->quoteName('#__mokosuitebackup_records')) - ->where($db->quoteName('profile_id') . ' = ' . $pid) - ->where($db->quoteName('status') . ' = ' . $db->quote('complete')) - ->order($db->quoteName('backupstart') . ' ASC'); - $db->setQuery($query, 0, $excess); - $oldest = $db->loadObjectList(); - - foreach ($oldest as $record) { - $this->deleteBackupRecord($db, $record); - } - } + RetentionManager::prune($db, $profile, $globalMaxAge, $globalMaxCount); } - // Also clean up orphaned records (profile deleted but records remain) - $query = $db->getQuery(true) - ->select('r.id, r.absolute_path') - ->from($db->quoteName('#__mokosuitebackup_records', 'r')) - ->join('LEFT', $db->quoteName('#__mokosuitebackup_profiles', 'p') . ' ON p.id = r.profile_id') - ->where('p.id IS NULL') - ->where($db->quoteName('r.status') . ' = ' . $db->quote('complete')); - $db->setQuery($query); - $orphans = $db->loadObjectList(); - - foreach ($orphans as $record) { - $this->deleteBackupRecord($db, $record); - } - } - - /** - * Delete a backup record and its archive file. - */ - private function deleteBackupRecord(object $db, object $record): void - { - if (!empty($record->absolute_path) && is_file($record->absolute_path)) { - if (!@unlink($record->absolute_path)) { - error_log('MokoSuiteBackup: Could not delete backup file (id=' . $record->id . '): ' . $record->absolute_path); - - return; - } - - $logPath = preg_replace('/\.(zip|tar\.gz)$/i', '.log', $record->absolute_path); - - if (is_file($logPath)) { - @unlink($logPath); - } - } - - try { - $db->setQuery( - $db->getQuery(true) - ->delete($db->quoteName('#__mokosuitebackup_records')) - ->where($db->quoteName('id') . ' = ' . (int) $record->id) - ); - $db->execute(); - } catch (\Exception $e) { - error_log('MokoSuiteBackup: Could not delete backup record ' . $record->id . ': ' . $e->getMessage()); - } + // Records whose profile was deleted (local files + DB row only). + RetentionManager::pruneOrphans($db); } /** From 7a829b6eca0d67ff75c35162aa637e64f673e27b Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 01:34:49 +0000 Subject: [PATCH 09/51] chore(version): pre-release bump to 02.58.06-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.06.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.06.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 6f668382..baee81b5 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.05 +# VERSION: 02.58.06 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 7ef06949..0ef3a52a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.05 +VERSION: 02.58.06 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 25d32596..9812805f 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.05 + 02.58.06 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.06.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.06.sql new file mode 100644 index 00000000..6f510638 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.06.sql @@ -0,0 +1 @@ +/* 02.58.06 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 1aef54bd..ccd64acb 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.05 + 02.58.06 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 780f2636..50710bf6 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.05 + 02.58.06 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 72c21305..c9457ba7 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.05 + 02.58.06 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index ad424664..2b6fbe93 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.05 + 02.58.06 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index a77ab126..e769f8da 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.05 + 02.58.06 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index fb0a7228..3a4a40a8 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.05 + 02.58.06 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 1b0f7b99..a0a584ef 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.05 + 02.58.06 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 3ad25bac..74ce1432 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.05 + 02.58.06 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 1d37622c..8ca57798 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.05 + 02.58.06 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 4027c727d917558c22cefa2b2b8554131eb89e4b Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 01:36:07 +0000 Subject: [PATCH 10/51] chore(version): pre-release bump to 02.58.07-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.07.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.07.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index baee81b5..6fb10088 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.06 +# VERSION: 02.58.07 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 0ef3a52a..85b3d126 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.06 +VERSION: 02.58.07 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 9812805f..0e7dc11d 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.06 + 02.58.07 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.07.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.07.sql new file mode 100644 index 00000000..0d01bce5 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.07.sql @@ -0,0 +1 @@ +/* 02.58.07 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index ccd64acb..c7c41770 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.06 + 02.58.07 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 50710bf6..1febd338 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.06 + 02.58.07 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index c9457ba7..05dbd7b5 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.06 + 02.58.07 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 2b6fbe93..5e084307 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.06 + 02.58.07 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index e769f8da..7c4f27d4 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.06 + 02.58.07 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 3a4a40a8..ad232c6c 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.06 + 02.58.07 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index a0a584ef..43bf9d17 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.06 + 02.58.07 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 74ce1432..931ce1a5 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.06 + 02.58.07 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 8ca57798..2f3c9c96 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.06 + 02.58.07 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 8dc4eaa384bf3ff723a675ca2a44c5db168f3b95 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 20:56:31 -0500 Subject: [PATCH 11/51] feat: pre-update backup notice + live-progress modal on update pages The server-side onExtensionBeforeUpdate backup runs synchronously and cannot drive a browser modal, so nothing ever 'popped'. Add a client notice + on-demand live-progress modal on com_joomlaupdate and com_installer(view=update): - plg_system_mokosuitebackup gains onBeforeCompileHead, which injects a 'back up before you update' notice + Back up now button on the update pages (gated by show_update_notice + backup_before_update), plus a new media JS asset that runs the same stepped backup as the dashboard (ajax.init then a loop of ajax.step) with a dependency-free progress overlay. - New AjaxController::preupdateAck() arms the same 10-min throttle key runPreActionBackup() checks, so proceeding with Joomla's update after a modal backup does not run a duplicate server-side backup. - Plugin manifest registers the new media/js folder; language keys added. Phase-1 baseline (notice + on-demand). Auto-intercepting the Update button is the planned phase-2 follow-up (#196). Refs #196 Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 1 + .../src/Controller/AjaxController.php | 29 +++ .../en-GB/plg_system_mokosuitebackup.ini | 7 + .../en-US/plg_system_mokosuitebackup.ini | 7 + .../media/js/update-backup.js | 202 ++++++++++++++++++ .../mokosuitebackup.xml | 4 + .../src/Extension/MokoSuiteBackup.php | 76 +++++++ 7 files changed, 326 insertions(+) create mode 100644 source/packages/plg_system_mokosuitebackup/media/js/update-backup.js diff --git a/CHANGELOG.md b/CHANGELOG.md index cb620142..0733ee39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Added +- Pre-update backup **notice + live-progress modal** on the admin update pages (Joomla Update and Extensions → Update). Because the server-side `onExtensionBeforeUpdate` backup runs synchronously and can't drive a browser modal, the system plugin now injects a "back up before you update" notice with a **Back up now** button that runs the same stepped backup as the dashboard and shows a live progress bar. On success it pings a new `ajax.preupdateAck` endpoint, which arms the same throttle the server-side hook checks — so clicking Joomla's Update afterwards won't run a duplicate backup. Gated by the existing `show_update_notice` + `backup_before_update` params. (Auto-intercepting the Update button is the planned phase-2 follow-up — see #196.) (#196) - Retention now prunes **remote** copies too: when a backup is pruned by age/count, its archive is deleted from every enabled remote destination (SFTP / FTP / S3 / Google Drive), not just the local copy. Each uploader gained an idempotent `delete()` method (already-absent file = success), and removal is best-effort — a failing destination is logged but never blocks local pruning. The shared standalone `restore.php` is intentionally left in place (every backup overwrites it, so newer backups still depend on it). (#229) ### Changed diff --git a/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php b/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php index 9d568ad7..cda1f601 100644 --- a/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php +++ b/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php @@ -84,6 +84,35 @@ class AjaxController extends BaseController $this->sendJson($result); } + /** + * Mark the pre-update backup as satisfied for this session. + * + * Called by the update-page modal after a successful on-demand backup so the + * imminent server-side onExtensionBeforeUpdate backup is skipped — it sets + * the same 10-minute throttle key the system plugin checks, preventing a + * duplicate backup when the admin then clicks Joomla's Update button. + * POST: task=ajax.preupdateAck + */ + public function preupdateAck(): void + { + if (!Session::checkToken('get') && !Session::checkToken('post')) { + $this->sendJson(['error' => true, 'message' => 'Invalid token'], 403); + + return; + } + + if (!$this->app->getIdentity()->authorise('mokosuitebackup.backup.run', 'com_mokosuitebackup')) { + $this->sendJson(['error' => true, 'message' => 'Access denied'], 403); + + return; + } + + // Same key + semantics as plg_system_mokosuitebackup::runPreActionBackup(). + Factory::getSession()->set('mokosuitebackup.preaction_backup_before_update', time()); + + $this->sendJson(['error' => false, 'message' => 'Pre-update backup acknowledged']); + } + /** * Cancel a backup record stuck in "running" status. * POST: task=ajax.cancelBackup&id=123 diff --git a/source/packages/plg_system_mokosuitebackup/language/en-GB/plg_system_mokosuitebackup.ini b/source/packages/plg_system_mokosuitebackup/language/en-GB/plg_system_mokosuitebackup.ini index d0804cd2..a465eb30 100644 --- a/source/packages/plg_system_mokosuitebackup/language/en-GB/plg_system_mokosuitebackup.ini +++ b/source/packages/plg_system_mokosuitebackup/language/en-GB/plg_system_mokosuitebackup.ini @@ -7,3 +7,10 @@ PLG_SYSTEM_MOKOJOOMBACKUP_FIELD_MAX_AGE="Max Backup Age (days)" PLG_SYSTEM_MOKOJOOMBACKUP_FIELD_MAX_AGE_DESC="Delete backup records older than this many days." PLG_SYSTEM_MOKOJOOMBACKUP_FIELD_MAX_BACKUPS="Max Backup Count" PLG_SYSTEM_MOKOJOOMBACKUP_FIELD_MAX_BACKUPS_DESC="Keep at most this many completed backup records." +PLG_SYSTEM_MOKOJOOMBACKUP_UPDATE_NOTICE="MokoSuiteBackup: a full-site backup is recommended before you update." +PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_NOW="Back up now" +PLG_SYSTEM_MOKOJOOMBACKUP_DISMISS="Dismiss" +PLG_SYSTEM_MOKOJOOMBACKUP_BACKING_UP="Backing up…" +PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE="Backup complete — safe to update." +PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED="Backup failed" +PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING="Starting backup…" diff --git a/source/packages/plg_system_mokosuitebackup/language/en-US/plg_system_mokosuitebackup.ini b/source/packages/plg_system_mokosuitebackup/language/en-US/plg_system_mokosuitebackup.ini index 15103fbb..03a35287 100644 --- a/source/packages/plg_system_mokosuitebackup/language/en-US/plg_system_mokosuitebackup.ini +++ b/source/packages/plg_system_mokosuitebackup/language/en-US/plg_system_mokosuitebackup.ini @@ -7,3 +7,10 @@ PLG_SYSTEM_MOKOJOOMBACKUP_FIELD_MAX_AGE="Max Backup Age (days)" PLG_SYSTEM_MOKOJOOMBACKUP_FIELD_MAX_AGE_DESC="Delete backup records older than this many days." PLG_SYSTEM_MOKOJOOMBACKUP_FIELD_MAX_BACKUPS="Max Backup Count" PLG_SYSTEM_MOKOJOOMBACKUP_FIELD_MAX_BACKUPS_DESC="Keep at most this many completed backup records." +PLG_SYSTEM_MOKOJOOMBACKUP_UPDATE_NOTICE="MokoSuiteBackup: a full-site backup is recommended before you update." +PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_NOW="Back up now" +PLG_SYSTEM_MOKOJOOMBACKUP_DISMISS="Dismiss" +PLG_SYSTEM_MOKOJOOMBACKUP_BACKING_UP="Backing up…" +PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE="Backup complete — safe to update." +PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED="Backup failed" +PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING="Starting backup…" diff --git a/source/packages/plg_system_mokosuitebackup/media/js/update-backup.js b/source/packages/plg_system_mokosuitebackup/media/js/update-backup.js new file mode 100644 index 00000000..493b45ea --- /dev/null +++ b/source/packages/plg_system_mokosuitebackup/media/js/update-backup.js @@ -0,0 +1,202 @@ +/** + * MokoSuiteBackup — pre-update backup notice + live-progress modal. + * + * Injected by plg_system_mokosuitebackup on the Joomla core-update + * (com_joomlaupdate) and extensions-update (com_installer&view=update) pages. + * + * Why a client-side modal: the server-side onExtensionBeforeUpdate backup runs + * synchronously mid-request and cannot drive a browser modal. This offers an + * on-demand "Back up now" button that runs the same stepped backup the + * dashboard uses (ajax.init then a loop of ajax.step) and shows live progress. + * On success it pings ajax.preupdateAck so the imminent server-side pre-update + * backup is skipped (no duplicate backup) when the admin clicks Update. + * + * Config comes from Joomla.getOptions('plg_system_mokosuitebackup.preupdate'). + */ +(function () { + 'use strict'; + + var cfg = (window.Joomla && Joomla.getOptions) + ? Joomla.getOptions('plg_system_mokosuitebackup.preupdate', null) + : null; + + if (!cfg || !cfg.ajaxUrl || !cfg.token) { + return; + } + + var L = cfg.labels || {}; + var running = false; + + document.addEventListener('DOMContentLoaded', function () { + injectNotice(); + }); + + function injectNotice() { + var host = document.querySelector('main') || document.querySelector('#content') || document.body; + + if (!host || document.getElementById('msb-preupdate-notice')) { + return; + } + + var bar = document.createElement('div'); + bar.id = 'msb-preupdate-notice'; + bar.setAttribute('role', 'status'); + bar.style.cssText = 'display:flex;align-items:center;gap:1rem;flex-wrap:wrap;' + + 'margin:0 0 1rem 0;padding:.75rem 1rem;border:1px solid #f0c36d;' + + 'background:#fcf8e3;border-radius:.35rem;color:#8a6d3b;font-size:.95rem;'; + + var text = document.createElement('span'); + text.style.cssText = 'flex:1 1 auto;'; + text.textContent = L.notice || 'A full-site backup is recommended before you update.'; + + var btn = document.createElement('button'); + btn.type = 'button'; + btn.id = 'msb-preupdate-run'; + btn.className = 'btn btn-warning btn-sm'; + btn.textContent = L.backupNow || 'Back up now'; + btn.addEventListener('click', runBackup); + + var dismiss = document.createElement('button'); + dismiss.type = 'button'; + dismiss.className = 'btn btn-link btn-sm'; + dismiss.style.cssText = 'color:#8a6d3b;'; + dismiss.textContent = L.dismiss || 'Dismiss'; + dismiss.addEventListener('click', function () { bar.remove(); }); + + bar.appendChild(text); + bar.appendChild(btn); + bar.appendChild(dismiss); + host.insertBefore(bar, host.firstChild); + } + + /* ── The live-progress overlay (dependency-free, mirrors the dashboard) ── */ + + function buildOverlay() { + var overlay = document.createElement('div'); + overlay.id = 'msb-preupdate-overlay'; + overlay.style.cssText = 'position:fixed;inset:0;z-index:10000;display:flex;' + + 'align-items:center;justify-content:center;background:rgba(0,0,0,.5);'; + + var box = document.createElement('div'); + box.style.cssText = 'width:min(480px,92vw);background:#fff;border-radius:.5rem;' + + 'padding:1.5rem;box-shadow:0 10px 40px rgba(0,0,0,.3);'; + + var title = document.createElement('h3'); + title.id = 'msb-pu-title'; + title.style.cssText = 'margin:0 0 1rem 0;font-size:1.15rem;'; + title.textContent = L.backingUp || 'Backing up…'; + + var track = document.createElement('div'); + track.style.cssText = 'height:1.25rem;background:#e9ecef;border-radius:.35rem;overflow:hidden;'; + + var barfill = document.createElement('div'); + barfill.id = 'msb-pu-bar'; + barfill.style.cssText = 'height:100%;width:0;background:#198754;transition:width .3s ease;'; + track.appendChild(barfill); + + var status = document.createElement('div'); + status.id = 'msb-pu-status'; + status.style.cssText = 'margin-top:.75rem;font-size:.9rem;color:#555;'; + status.textContent = L.starting || 'Starting backup…'; + + box.appendChild(title); + box.appendChild(track); + box.appendChild(status); + overlay.appendChild(box); + document.body.appendChild(overlay); + + return { overlay: overlay, title: title, bar: barfill, status: status }; + } + + function setProgress(ui, progress, message) { + var pct = Math.max(0, Math.min(100, parseInt(progress, 10) || 0)); + ui.bar.style.width = pct + '%'; + + if (message) { + ui.status.textContent = message; + } + } + + function post(params) { + var body = new URLSearchParams(); + body.append(cfg.token, '1'); + + Object.keys(params).forEach(function (k) { + body.append(k, params[k]); + }); + + return fetch(cfg.ajaxUrl, { + method: 'POST', + body: body, + headers: { 'X-Requested-With': 'XMLHttpRequest' } + }).then(function (r) { return r.json(); }); + } + + async function runBackup() { + if (running) { + return; + } + + running = true; + + var runBtn = document.getElementById('msb-preupdate-run'); + + if (runBtn) { + runBtn.disabled = true; + } + + var ui = buildOverlay(); + var guard = function (e) { e.preventDefault(); e.returnValue = ''; }; + window.addEventListener('beforeunload', guard); + + try { + var init = await post({ task: 'ajax.init', profile_id: cfg.profileId }); + + if (!init || init.error || !init.session_id) { + throw new Error((init && init.message) || 'Could not start backup'); + } + + setProgress(ui, init.progress, init.message); + + var done = false; + + while (!done) { + var step = await post({ task: 'ajax.step', session_id: init.session_id }); + + if (!step || step.error) { + throw new Error((step && step.message) || 'Backup step failed'); + } + + setProgress(ui, step.progress, step.message); + done = step.done || false; + } + + /* Tell the server the pre-update backup is satisfied so the + synchronous onExtensionBeforeUpdate backup is skipped. */ + try { await post({ task: 'ajax.preupdateAck' }); } catch (ignore) {} + + ui.title.textContent = L.complete || 'Backup complete — safe to update.'; + setProgress(ui, 100, ''); + + var notice = document.getElementById('msb-preupdate-notice'); + + if (notice) { + notice.style.background = '#d1e7dd'; + notice.style.borderColor = '#badbcc'; + notice.style.color = '#0f5132'; + } + + setTimeout(function () { ui.overlay.remove(); }, 1500); + } catch (err) { + ui.title.textContent = (L.failed || 'Backup failed') + ': ' + err.message; + ui.bar.style.background = '#dc3545'; + + if (runBtn) { + runBtn.disabled = false; + } + } finally { + running = false; + window.removeEventListener('beforeunload', guard); + } + } +})(); diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index ad232c6c..2aa8e82d 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -29,6 +29,10 @@ language/en-GB/plg_system_mokosuitebackup.sys.ini + + js + +
diff --git a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php index 6ec0c2d6..21690564 100644 --- a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php +++ b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php @@ -13,8 +13,13 @@ namespace Joomla\Plugin\System\MokoSuiteBackup\Extension; defined('_JEXEC') or die; use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Document\HtmlDocument; use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; use Joomla\CMS\Plugin\CMSPlugin; +use Joomla\CMS\Router\Route; +use Joomla\CMS\Session\Session; +use Joomla\CMS\Uri\Uri; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RetentionManager; use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner; use Joomla\Event\Event; @@ -29,6 +34,7 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface return [ 'onAfterInitialise' => 'onAfterInitialise', 'onAfterRoute' => 'onAfterRoute', + 'onBeforeCompileHead' => 'onBeforeCompileHead', 'onExtensionBeforeUpdate' => 'onExtensionBeforeUpdate', 'onExtensionBeforeUninstall' => 'onExtensionBeforeUninstall', ]; @@ -139,6 +145,76 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface $this->cleanupOldSnapshots(); } + /** + * Inject the pre-update backup notice + live-progress modal on the admin + * update pages (com_joomlaupdate and com_installer's update view). + * + * The server-side onExtensionBeforeUpdate backup runs synchronously and + * cannot drive a browser modal, so this offers an on-demand "Back up now" + * button that runs the same stepped backup as the dashboard and shows live + * progress. On success the client pings ajax.preupdateAck, which arms the + * same throttle key runPreActionBackup() checks — so the subsequent server + * update does not run a duplicate backup. + */ + public function onBeforeCompileHead(Event $event): void + { + $app = $this->getApplication(); + + if (!$app->isClient('administrator')) { + return; + } + + $input = $app->getInput(); + $option = $input->getCmd('option', ''); + $view = $input->getCmd('view', ''); + + $onUpdatePage = $option === 'com_joomlaupdate' + || ($option === 'com_installer' && $view === 'update'); + + if (!$onUpdatePage) { + return; + } + + $params = ComponentHelper::getParams('com_mokosuitebackup'); + + // Respect the notice toggle and the pre-update backup feature flag. + if (!(int) $params->get('show_update_notice', 1) || !(int) $params->get('backup_before_update', 0)) { + return; + } + + // Already backed up this session (within the throttle window)? Stay quiet. + $lastRun = (int) Factory::getSession()->get('mokosuitebackup.preaction_backup_before_update', 0); + + if ($lastRun > 0 && (time() - $lastRun) < 600) { + return; + } + + $doc = $app->getDocument(); + + if (!$doc instanceof HtmlDocument) { + return; + } + + $doc->getWebAssetManager()->useScript('core'); + + $doc->addScriptOptions('plg_system_mokosuitebackup.preupdate', [ + 'ajaxUrl' => Route::_('index.php?option=com_mokosuitebackup&format=json', false), + 'token' => Session::getFormToken(), + 'profileId' => (int) $params->get('default_profile', 1), + 'labels' => [ + 'notice' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_UPDATE_NOTICE'), + 'backupNow' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_NOW'), + 'dismiss' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_DISMISS'), + 'backingUp' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKING_UP'), + 'starting' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING'), + 'complete' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE'), + 'failed' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED'), + ], + ]); + + $doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/update-backup.js', [], ['defer' => true]); + } + /** * Remove backup records and files per profile retention settings. * Each profile can override the global max_age_days and max_backups. From 6060979ce81f08d135f308e9269cb8136a50636e Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 01:58:00 +0000 Subject: [PATCH 12/51] chore(version): pre-release bump to 02.58.08-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.08.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.08.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 6fb10088..8c82b8ba 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.07 +# VERSION: 02.58.08 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 85b3d126..bd4705fe 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.07 +VERSION: 02.58.08 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 0e7dc11d..220d65a6 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.07 + 02.58.08 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.08.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.08.sql new file mode 100644 index 00000000..e3bd5427 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.08.sql @@ -0,0 +1 @@ +/* 02.58.08 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index c7c41770..b514511b 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.07 + 02.58.08 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 1febd338..59b256d0 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.07 + 02.58.08 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 05dbd7b5..f1422330 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.07 + 02.58.08 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 5e084307..385d141b 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.07 + 02.58.08 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 7c4f27d4..7eff54c1 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.07 + 02.58.08 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 2aa8e82d..3ca7c60f 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.07 + 02.58.08 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 43bf9d17..cccc57f6 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.07 + 02.58.08 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 931ce1a5..a06e3ec3 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.07 + 02.58.08 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 2f3c9c96..3f5cd9c3 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.07 + 02.58.08 2026-06-02 Moko Consulting hello@mokoconsulting.tech From c0ecf6ce730f0622dfd0658ef13cc715cbd870fa Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 21:13:35 -0500 Subject: [PATCH 13/51] =?UTF-8?q?feat:=20pre-update=20modal=20phase=202=20?= =?UTF-8?q?=E2=80=94=20auto-run=20backup=20on=20Update=20click=20(#196)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opt-in param preupdate_auto_intercept (off by default). When enabled the injected update-page script wraps window.Joomla.submitbutton so clicking the toolbar Update button first pops the progress modal, runs the pre-update backup, and re-issues the original update task on success. - Matches only update.update / update.install (not Find Updates / Clear Cache, which also start with 'update'). - Reuses the phase-1 overlay + ajax.preupdateAck throttle so the subsequent server-side onExtensionBeforeUpdate backup is skipped. - The manual 'Back up now' notice remains a fallback for flows that don't route through submitbutton (e.g. some com_joomlaupdate paths). Refs #196 Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 3 +- .../packages/com_mokosuitebackup/config.xml | 12 ++++ .../language/en-GB/com_mokosuitebackup.ini | 2 + .../language/en-US/com_mokosuitebackup.ini | 2 + .../media/js/update-backup.js | 59 +++++++++++++++++++ .../src/Extension/MokoSuiteBackup.php | 7 ++- 6 files changed, 81 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0733ee39..e7e17c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## [Unreleased] ### Added -- Pre-update backup **notice + live-progress modal** on the admin update pages (Joomla Update and Extensions → Update). Because the server-side `onExtensionBeforeUpdate` backup runs synchronously and can't drive a browser modal, the system plugin now injects a "back up before you update" notice with a **Back up now** button that runs the same stepped backup as the dashboard and shows a live progress bar. On success it pings a new `ajax.preupdateAck` endpoint, which arms the same throttle the server-side hook checks — so clicking Joomla's Update afterwards won't run a duplicate backup. Gated by the existing `show_update_notice` + `backup_before_update` params. (Auto-intercepting the Update button is the planned phase-2 follow-up — see #196.) (#196) +- Pre-update backup **notice + live-progress modal** on the admin update pages (Joomla Update and Extensions → Update). Because the server-side `onExtensionBeforeUpdate` backup runs synchronously and can't drive a browser modal, the system plugin now injects a "back up before you update" notice with a **Back up now** button that runs the same stepped backup as the dashboard and shows a live progress bar. On success it pings a new `ajax.preupdateAck` endpoint, which arms the same throttle the server-side hook checks — so clicking Joomla's Update afterwards won't run a duplicate backup. Gated by the existing `show_update_notice` + `backup_before_update` params. (#196) +- Pre-update modal **phase 2 — "Auto-run on Update click"** (new opt-in param `preupdate_auto_intercept`, off by default). When enabled, clicking the toolbar **Update** button on the Joomla Update / Extensions-update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. Implemented by wrapping `Joomla.submitbutton` for the `update.update` / `update.install` tasks only (Find Updates / Clear Cache are untouched); the manual "Back up now" notice remains as a fallback for any flow that doesn't route through `submitbutton`. (#196) - Retention now prunes **remote** copies too: when a backup is pruned by age/count, its archive is deleted from every enabled remote destination (SFTP / FTP / S3 / Google Drive), not just the local copy. Each uploader gained an idempotent `delete()` method (already-absent file = success), and removal is best-effort — a failing destination is logged but never blocks local pruning. The shared standalone `restore.php` is intentionally left in place (every backup overwrites it, so newer backups still depend on it). (#229) ### Changed diff --git a/source/packages/com_mokosuitebackup/config.xml b/source/packages/com_mokosuitebackup/config.xml index 84db7fca..94650c5b 100644 --- a/source/packages/com_mokosuitebackup/config.xml +++ b/source/packages/com_mokosuitebackup/config.xml @@ -151,6 +151,18 @@ + + + + getWebAssetManager()->useScript('core'); $doc->addScriptOptions('plg_system_mokosuitebackup.preupdate', [ - 'ajaxUrl' => Route::_('index.php?option=com_mokosuitebackup&format=json', false), - 'token' => Session::getFormToken(), - 'profileId' => (int) $params->get('default_profile', 1), + 'ajaxUrl' => Route::_('index.php?option=com_mokosuitebackup&format=json', false), + 'token' => Session::getFormToken(), + 'profileId' => (int) $params->get('default_profile', 1), + 'autoIntercept' => (bool) (int) $params->get('preupdate_auto_intercept', 0), 'labels' => [ 'notice' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_UPDATE_NOTICE'), 'backupNow' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_NOW'), From a47a9e5debdbb09b924baaf372de822190cd5d44 Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 02:14:34 +0000 Subject: [PATCH 14/51] chore(version): pre-release bump to 02.58.09-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.09.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.09.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 8c82b8ba..712e9542 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.08 +# VERSION: 02.58.09 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index bd4705fe..baaa930c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.08 +VERSION: 02.58.09 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 220d65a6..9e537087 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.08 + 02.58.09 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.09.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.09.sql new file mode 100644 index 00000000..7f58fde3 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.09.sql @@ -0,0 +1 @@ +/* 02.58.09 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index b514511b..e2927718 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.08 + 02.58.09 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 59b256d0..a91eef35 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.08 + 02.58.09 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index f1422330..3e6037c4 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.08 + 02.58.09 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 385d141b..614b78e4 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.08 + 02.58.09 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 7eff54c1..9d574b3f 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.08 + 02.58.09 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 3ca7c60f..f1bd643a 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.08 + 02.58.09 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index cccc57f6..6b2a179a 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.08 + 02.58.09 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index a06e3ec3..a9247954 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.08 + 02.58.09 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 3f5cd9c3..d5975187 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.08 + 02.58.09 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 5ffcbcc2f139d4dd232f2247c3136fbc3cc9bdda Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 22:02:46 -0500 Subject: [PATCH 15/51] fix: CLI backups no longer named joomla.invalid (host placeholder) Joomla's console fills $_SERVER['HTTP_HOST'] with the reserved sentinel 'joomla.invalid', which the [HOST] placeholder resolver used verbatim because its unusable-host check only knew about empty/localhost. Treat 'joomla.invalid' as unusable too, falling back to live_site then the system hostname, so CLI/console/scheduled backups get a real name. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 1 + .../src/Engine/PlaceholderResolver.php | 29 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e17c2c..8209fef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - The backend controller, Web Services API controller, and legacy `cli/mokosuitebackup.php` now run backups through the shared `BackupRunner` (gaining the normalized complete/warning/fail status) instead of instantiating `BackupEngine` directly. ### Fixed +- Archive names for **CLI/console-triggered backups** no longer come out as `joomla.invalid_…`. The `[HOST]` placeholder took `$_SERVER['HTTP_HOST']` verbatim, but Joomla's console fills that with the reserved sentinel host `joomla.invalid`; the resolver now treats that (like empty/`localhost`) as unusable and falls back to the configured `live_site` host, then the system hostname. (Set the site's *live_site* to get the exact domain in CLI-built names.) - Standalone restore script generation no longer aborts backups with `str_replace() expects at least 3 arguments, 2 given`. `MokoRestore::generateStandaloneScript()` had a `str_replace()` call (the "Backup Archive" pre-check rewrite) that was missing its `$php` subject argument, so **every** standalone-mode backup fatally errored while "Generating standalone restore.php…" — the archive still finalized and uploaded, but no `restore.php` was ever produced (the true root cause behind #226). (#226) - Remote upload: the standalone restore script upload is no longer silent — its result is now checked and logged, and a failed restore-script upload marks the backup as `warning` (previously the result was discarded, so a missing restore script on the remote went unreported while the archive still showed success). (#226) diff --git a/source/packages/com_mokosuitebackup/src/Engine/PlaceholderResolver.php b/source/packages/com_mokosuitebackup/src/Engine/PlaceholderResolver.php index 2d6b5486..cccbb0aa 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/PlaceholderResolver.php +++ b/source/packages/com_mokosuitebackup/src/Engine/PlaceholderResolver.php @@ -52,15 +52,25 @@ class PlaceholderResolver { $now = new \DateTimeImmutable('now'); - /* Resolve hostname: prefer HTTP_HOST (web), then try Joomla config (CLI), then system hostname */ - $rawHost = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? ''; + /* Resolve hostname: prefer the real request host (web), then the + configured live_site (CLI), then the system hostname. Joomla's console + fills the request host with the placeholder 'joomla.invalid' (an + RFC 6761 reserved TLD used as a CLI sentinel), so treat that — along + with empty/localhost — as unusable and fall through; otherwise every + CLI-triggered backup would be named 'joomla.invalid_…'. */ + $unusable = static function (string $h): bool { + $h = strtolower(trim($h)); - if (empty($rawHost) || $rawHost === 'localhost') { + return $h === '' || $h === 'localhost' || $h === 'joomla.invalid'; + }; + + $rawHost = (string) ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? ''); + + if ($unusable($rawHost)) { try { - $app = Factory::getApplication(); - $liveSite = $app->get('live_site', ''); + $liveSite = (string) Factory::getApplication()->get('live_site', ''); - if (!empty($liveSite)) { + if ($liveSite !== '') { $parsed = parse_url($liveSite, PHP_URL_HOST); if (!empty($parsed)) { @@ -68,12 +78,13 @@ class PlaceholderResolver } } } catch (\Throwable $e) { - /* fallback */ + /* fall through to system hostname */ } } - if (empty($rawHost)) { - $rawHost = php_uname('n'); + if ($unusable($rawHost)) { + $sysHost = php_uname('n'); + $rawHost = $sysHost !== '' ? $sysHost : 'site'; } $hostname = preg_replace('/[^a-zA-Z0-9._-]/', '', $rawHost); From 7cb0b08cfe99de0954b5c2a26f263b37d567fb82 Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 03:03:24 +0000 Subject: [PATCH 16/51] chore(version): pre-release bump to 02.58.10-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.10.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.10.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 712e9542..5613f739 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.09 +# VERSION: 02.58.10 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index baaa930c..c917b732 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.09 +VERSION: 02.58.10 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 9e537087..fa902620 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.09 + 02.58.10 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.10.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.10.sql new file mode 100644 index 00000000..4aaacaa2 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.10.sql @@ -0,0 +1 @@ +/* 02.58.10 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index e2927718..7108537e 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.09 + 02.58.10 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index a91eef35..e47f8275 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.09 + 02.58.10 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 3e6037c4..241ba8ea 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.09 + 02.58.10 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 614b78e4..2ff94331 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.09 + 02.58.10 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 9d574b3f..75174db3 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.09 + 02.58.10 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index f1bd643a..398b435b 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.09 + 02.58.10 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 6b2a179a..e514cc15 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.09 + 02.58.10 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index a9247954..1c88d968 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.09 + 02.58.10 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index d5975187..0c683d69 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.09 + 02.58.10 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 07cbe5de5224da6107b7b02248a3f3eb98793372 Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 03:04:04 +0000 Subject: [PATCH 17/51] chore(version): pre-release bump to 02.58.11-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.11.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.11.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 5613f739..3c0f5bde 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.10 +# VERSION: 02.58.11 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index c917b732..db67b4fc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.10 +VERSION: 02.58.11 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index fa902620..b3c8ebd0 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.10 + 02.58.11 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.11.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.11.sql new file mode 100644 index 00000000..aec6c0bf --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.11.sql @@ -0,0 +1 @@ +/* 02.58.11 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 7108537e..654574b9 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.10 + 02.58.11 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index e47f8275..41c7e476 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.10 + 02.58.11 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 241ba8ea..79ff002f 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.10 + 02.58.11 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 2ff94331..2e070f81 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.10 + 02.58.11 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 75174db3..b117777d 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.10 + 02.58.11 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 398b435b..4ac410f3 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.10 + 02.58.11 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index e514cc15..0a2b0d2f 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.10 + 02.58.11 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 1c88d968..29cc2bf0 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.10 + 02.58.11 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 0c683d69..8407656d 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.10 + 02.58.11 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 49463eb74723149bd52cc438b77ce6078dd9cf51 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 22:33:43 -0500 Subject: [PATCH 18/51] fix: raise resource limits for synchronous pre-update backup (WSOD) The pre-update/uninstall backup runs synchronously inside the extension update request. Without raising limits, a large site exceeds max_execution_time/memory_limit mid-backup and the update page white-screens. Raise set_time_limit(0)/memory_limit/ignore_user_abort like the web-cron path. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 1 + .../src/Extension/MokoSuiteBackup.php | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8209fef5..57002893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - The backend controller, Web Services API controller, and legacy `cli/mokosuitebackup.php` now run backups through the shared `BackupRunner` (gaining the normalized complete/warning/fail status) instead of instantiating `BackupEngine` directly. ### Fixed +- Pre-update/uninstall backup no longer **white-screens** the update on large sites. The synchronous backup that runs inside the extension update/uninstall request now raises `max_execution_time`/`memory_limit` (and `ignore_user_abort`) like the web-cron path, so it can't exhaust the request's default limits mid-backup. (Core Joomla updates additionally get a full-screen backup screen — see below.) - Archive names for **CLI/console-triggered backups** no longer come out as `joomla.invalid_…`. The `[HOST]` placeholder took `$_SERVER['HTTP_HOST']` verbatim, but Joomla's console fills that with the reserved sentinel host `joomla.invalid`; the resolver now treats that (like empty/`localhost`) as unusable and falls back to the configured `live_site` host, then the system hostname. (Set the site's *live_site* to get the exact domain in CLI-built names.) - Standalone restore script generation no longer aborts backups with `str_replace() expects at least 3 arguments, 2 given`. `MokoRestore::generateStandaloneScript()` had a `str_replace()` call (the "Backup Archive" pre-check rewrite) that was missing its `$php` subject argument, so **every** standalone-mode backup fatally errored while "Generating standalone restore.php…" — the archive still finalized and uploaded, but no `restore.php` was ever produced (the true root cause behind #226). (#226) - Remote upload: the standalone restore script upload is no longer silent — its result is now checked and logged, and a failed restore-script upload marks the backup as `warning` (previously the result was discarded, so a missing restore script on the remote went unreported while the archive still showed success). (#226) diff --git a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php index 07146b69..b2399603 100644 --- a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php +++ b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php @@ -386,6 +386,16 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface $profileId = (int) $params->get('default_profile', 1); + /* This backup runs synchronously inside the extension update/uninstall + request. Without raising these limits a large site blows past + max_execution_time / memory_limit mid-backup and the update page + white-screens. Mirror the web-cron path. (Core Joomla updates are + handled by the full-screen redirect instead — see onAfterRoute.) */ + @set_time_limit(0); + @ini_set('max_execution_time', '0'); + @ini_set('memory_limit', '512M'); + @ignore_user_abort(true); + try { $result = (new BackupRunner())->run($profileId, $description, 'preaction'); $app = Factory::getApplication(); From 064963da9c37b78e38722e36dba6f1cd5ba5e3e4 Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 03:34:09 +0000 Subject: [PATCH 19/51] chore(version): pre-release bump to 02.58.12-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.12.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.12.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 3c0f5bde..87de9d79 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.11 +# VERSION: 02.58.12 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index db67b4fc..dd0154c4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.11 +VERSION: 02.58.12 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index b3c8ebd0..a65b9c47 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.11 + 02.58.12 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.12.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.12.sql new file mode 100644 index 00000000..296d5bd0 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.12.sql @@ -0,0 +1 @@ +/* 02.58.12 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 654574b9..fe97a566 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.11 + 02.58.12 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 41c7e476..0fc5da8f 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.11 + 02.58.12 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 79ff002f..4b651873 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.11 + 02.58.12 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 2e070f81..eb0bb752 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.11 + 02.58.12 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index b117777d..b83ba1a7 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.11 + 02.58.12 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 4ac410f3..93ebdd58 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.11 + 02.58.12 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 0a2b0d2f..be854658 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.11 + 02.58.12 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 29cc2bf0..219887fe 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.11 + 02.58.12 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 8407656d..45da2583 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.11 + 02.58.12 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 85c16b4af4e0ba8c7240eb18f1f71d04c58d29ee Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 03:34:43 +0000 Subject: [PATCH 20/51] chore(version): pre-release bump to 02.58.13-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.13.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.13.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 87de9d79..087c7c15 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.12 +# VERSION: 02.58.13 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index dd0154c4..41652fd4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.12 +VERSION: 02.58.13 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index a65b9c47..740cb258 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.12 + 02.58.13 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.13.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.13.sql new file mode 100644 index 00000000..0d546c54 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.13.sql @@ -0,0 +1 @@ +/* 02.58.13 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index fe97a566..67c51dab 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.12 + 02.58.13 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 0fc5da8f..80cd9113 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.12 + 02.58.13 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 4b651873..10d438da 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.12 + 02.58.13 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index eb0bb752..1d346801 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.12 + 02.58.13 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index b83ba1a7..cd89f6e1 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.12 + 02.58.13 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 93ebdd58..fa1a689e 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.12 + 02.58.13 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index be854658..440ae4b4 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.12 + 02.58.13 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 219887fe..09537509 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.12 + 02.58.13 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 45da2583..b2c5c2b4 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.12 + 02.58.13 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 8cfa46d985d66b0b785364a86834bf6ca76cca0a Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 22:55:28 -0500 Subject: [PATCH 21/51] feat: full-screen backup screen for pre-update + Backup Now (Akeeba-style) Replace the popup-modal pre-update approach with a dedicated full-page backup screen, modelled on Akeeba Backup-on-Update, and fix the white screen caused by running a backup synchronously inside the update request. - New view=runbackup (View/Runbackup + tmpl/runbackup): full-screen progress page that auto-starts the stepped backup (ajax.init -> loop ajax.step), then redirects to a validated returnurl on completion or shows a completion/retry panel. Auto-resolved by MVCFactory. - System plugin: on com_joomlaupdate task=update.install (when backup_before_update is on), redirect to the full-screen backup screen with returnurl back to update.install&is_backed_up=1; on return, arm the throttle so onExtensionBeforeUpdate does not run a duplicate backup. No synchronous backup in the update request. - Dashboard 'Backup Now' navigates to the full-screen screen instead of the inline modal; the inline modal + stepped JS are removed. - Retire the popup modal: delete media/js/update-backup.js, remove the plugin element and the preupdate_auto_intercept param. - Dashboard: warn when Global Config live_site is empty (CLI/scheduled backup naming + URL generation depend on it). Refs #196 Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 3 +- .../packages/com_mokosuitebackup/config.xml | 12 - .../language/en-GB/com_mokosuitebackup.ini | 15 + .../language/en-US/com_mokosuitebackup.ini | 15 + .../src/View/Runbackup/HtmlView.php | 89 ++++++ .../tmpl/dashboard/default.php | 140 ++-------- .../tmpl/runbackup/default.php | 240 ++++++++++++++++ .../media/js/update-backup.js | 261 ------------------ .../mokosuitebackup.xml | 4 - .../src/Extension/MokoSuiteBackup.php | 111 ++++---- 10 files changed, 442 insertions(+), 448 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/src/View/Runbackup/HtmlView.php create mode 100644 source/packages/com_mokosuitebackup/tmpl/runbackup/default.php delete mode 100644 source/packages/plg_system_mokosuitebackup/media/js/update-backup.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 57002893..f41d813c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,7 @@ ## [Unreleased] ### Added -- Pre-update backup **notice + live-progress modal** on the admin update pages (Joomla Update and Extensions → Update). Because the server-side `onExtensionBeforeUpdate` backup runs synchronously and can't drive a browser modal, the system plugin now injects a "back up before you update" notice with a **Back up now** button that runs the same stepped backup as the dashboard and shows a live progress bar. On success it pings a new `ajax.preupdateAck` endpoint, which arms the same throttle the server-side hook checks — so clicking Joomla's Update afterwards won't run a duplicate backup. Gated by the existing `show_update_notice` + `backup_before_update` params. (#196) -- Pre-update modal **phase 2 — "Auto-run on Update click"** (new opt-in param `preupdate_auto_intercept`, off by default). When enabled, clicking the toolbar **Update** button on the Joomla Update / Extensions-update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. Implemented by wrapping `Joomla.submitbutton` for the `update.update` / `update.install` tasks only (Find Updates / Clear Cache are untouched); the manual "Back up now" notice remains as a fallback for any flow that doesn't route through `submitbutton`. (#196) +- **Full-screen backup screen** (`view=runbackup`) for both pre-update and manual backups, modelled on Akeeba's Backup-on-Update — replaces the earlier popup-modal approach. Clicking Joomla's **Install the update** now redirects (server-side, Akeeba-style) to a dedicated full-page backup screen that runs the stepped backup with a real progress bar and then **automatically continues the update** via a validated `returnurl` (flagged `is_backed_up=1` so the backup isn't repeated). Crucially, **no backup runs synchronously inside the update request** — which is what white-screened large sites. The dashboard **Backup Now** now opens the same full-screen screen instead of an inline modal. (#196) - Retention now prunes **remote** copies too: when a backup is pruned by age/count, its archive is deleted from every enabled remote destination (SFTP / FTP / S3 / Google Drive), not just the local copy. Each uploader gained an idempotent `delete()` method (already-absent file = success), and removal is best-effort — a failing destination is logged but never blocks local pruning. The shared standalone `restore.php` is intentionally left in place (every backup overwrites it, so newer backups still depend on it). (#229) ### Changed diff --git a/source/packages/com_mokosuitebackup/config.xml b/source/packages/com_mokosuitebackup/config.xml index 94650c5b..84db7fca 100644 --- a/source/packages/com_mokosuitebackup/config.xml +++ b/source/packages/com_mokosuitebackup/config.xml @@ -151,18 +151,6 @@ - - - - + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + */ + +namespace Joomla\Component\MokoSuiteBackup\Administrator\View\Runbackup; + +defined('_JEXEC') or die; + +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; +use Joomla\CMS\Toolbar\ToolbarHelper; + +/** + * Full-screen "run a backup" progress view. + * + * Auto-starts the stepped backup (ajax.init → loop ajax.step) and shows a + * full-page progress screen. Used both by the dashboard "Backup Now" action + * and — via the system plugin's pre-update redirect — as the interstitial + * between clicking Joomla's Update and the update actually running. When a + * `returnurl` is supplied the page redirects there once the backup completes + * (e.g. back to `com_joomlaupdate&task=update.install&is_backed_up=1`). + */ +class HtmlView extends BaseHtmlView +{ + public int $profileId = 1; + + public string $profileTitle = ''; + + public string $description = ''; + + /** Raw (possibly base64) return URL from the request; validated in the layout. */ + public string $returnUrl = ''; + + public bool $autostart = true; + + public function display($tpl = null): void + { + $input = Factory::getApplication()->getInput(); + + $this->profileId = (int) $input->getInt('profile_id', $input->getInt('profileid', 1)); + $this->description = $input->getString('description', ''); + $this->returnUrl = $input->getRaw('returnurl', ''); + $this->autostart = (bool) $input->getInt('autostart', 1); + + if ($this->profileId <= 0) { + $this->profileId = 1; + } + + $this->profileTitle = $this->loadProfileTitle($this->profileId); + + $this->addToolbar(); + + parent::display($tpl); + } + + /** + * Look up the target profile's title for display (best-effort). + */ + private function loadProfileTitle(int $profileId): string + { + try { + $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class); + $query = $db->getQuery(true) + ->select($db->quoteName('title')) + ->from($db->quoteName('#__mokosuitebackup_profiles')) + ->where($db->quoteName('id') . ' = ' . (int) $profileId); + $db->setQuery($query); + + return (string) ($db->loadResult() ?? ''); + } catch (\Throwable $e) { + return ''; + } + } + + protected function addToolbar(): void + { + ToolbarHelper::title( + Text::_('COM_MOKOJOOMBACKUP_SHORT') . ': ' . Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE'), + 'archive' + ); + } +} diff --git a/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php b/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php index 565acd9e..e861acdc 100644 --- a/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php +++ b/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php @@ -17,6 +17,9 @@ use Joomla\CMS\Session\Session; $ajaxToken = Session::getFormToken(); $ajaxUrl = Route::_('index.php?option=com_mokosuitebackup&format=json', false); + +$runbackupUrl = Route::_('index.php?option=com_mokosuitebackup&view=runbackup&autostart=1', false); +$liveSite = trim((string) \Joomla\CMS\Factory::getApplication()->get('live_site', '')); ?> defaultDirWarning) : ?> + + + +
@@ -244,7 +257,7 @@ document.querySelectorAll('.mb-tile').forEach(function(tile) { - @@ -302,123 +315,18 @@ document.querySelectorAll('.mb-tile').forEach(function(tile) {
- - - diff --git a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php new file mode 100644 index 00000000..d4cceddd --- /dev/null +++ b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php @@ -0,0 +1,240 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + * + * Full-screen backup progress screen. Reuses the stepped-backup AJAX + * (ajax.init → loop ajax.step). On completion, redirects to a validated + * return URL (pre-update flow) or shows a completion panel (Backup Now). + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\Language\Text; +use Joomla\CMS\Router\Route; +use Joomla\CMS\Session\Session; +use Joomla\CMS\Uri\Uri; + +$ajaxToken = Session::getFormToken(); +$ajaxUrl = Route::_('index.php?option=com_mokosuitebackup&format=json', false); + +/* Validate the return URL to prevent an open redirect: accept only a relative + URL or one whose host matches this site. Falls back to '' (no redirect). */ +$safeReturnUrl = ''; + +if ($this->returnUrl !== '') { + $decoded = base64_decode($this->returnUrl, true); + $raw = ($decoded !== false && $decoded !== '') ? $decoded : $this->returnUrl; + + try { + $rootHost = Uri::getInstance(Uri::root())->getHost(); + $target = new Uri($raw); + + if ($target->getHost() === '' || strcasecmp($target->getHost(), $rootHost) === 0) { + $safeReturnUrl = $raw; + } + } catch (\Throwable $e) { + $safeReturnUrl = ''; + } +} + +$dashboardUrl = Route::_('index.php?option=com_mokosuitebackup&view=dashboard', false); + +$config = [ + 'ajaxUrl' => $ajaxUrl, + 'token' => $ajaxToken, + 'profileId' => $this->profileId, + 'description' => $this->description, + 'returnUrl' => $safeReturnUrl, + 'dashboardUrl' => $dashboardUrl, + 'autostart' => $this->autostart, + 'labels' => [ + 'starting' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), + 'running' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_RUNNING'), + 'complete' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_COMPLETE'), + 'continuing' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUING'), + 'failed' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED'), + 'retry' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY'), + 'continue' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY'), + 'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'), + 'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'), + ], +]; +?> +
+
+
+

+ + +

+

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

+ +
+
0%
+
+ +
+
+ +
+ + + +
+
+
+
+ + diff --git a/source/packages/plg_system_mokosuitebackup/media/js/update-backup.js b/source/packages/plg_system_mokosuitebackup/media/js/update-backup.js deleted file mode 100644 index 5cdc53d2..00000000 --- a/source/packages/plg_system_mokosuitebackup/media/js/update-backup.js +++ /dev/null @@ -1,261 +0,0 @@ -/** - * MokoSuiteBackup — pre-update backup notice + live-progress modal. - * - * Injected by plg_system_mokosuitebackup on the Joomla core-update - * (com_joomlaupdate) and extensions-update (com_installer&view=update) pages. - * - * Why a client-side modal: the server-side onExtensionBeforeUpdate backup runs - * synchronously mid-request and cannot drive a browser modal. This offers an - * on-demand "Back up now" button that runs the same stepped backup the - * dashboard uses (ajax.init then a loop of ajax.step) and shows live progress. - * On success it pings ajax.preupdateAck so the imminent server-side pre-update - * backup is skipped (no duplicate backup) when the admin clicks Update. - * - * Config comes from Joomla.getOptions('plg_system_mokosuitebackup.preupdate'). - */ -(function () { - 'use strict'; - - var cfg = (window.Joomla && Joomla.getOptions) - ? Joomla.getOptions('plg_system_mokosuitebackup.preupdate', null) - : null; - - if (!cfg || !cfg.ajaxUrl || !cfg.token) { - return; - } - - var L = cfg.labels || {}; - var running = false; - var backedUp = false; - - document.addEventListener('DOMContentLoaded', function () { - injectNotice(); - }); - - if (cfg.autoIntercept) { - setupInterception(); - } - - /* ── Phase 2: auto-run on the Update click ────────────────────────────── - * Wrap window.Joomla.submitbutton so clicking the toolbar Update button - * first runs the backup modal, then continues the original update once the - * backup succeeds. submitbutton is synchronous, so we can't pause it — we - * swallow the update task, run the async backup, and re-issue the original - * task ourselves on success. The notice's "Back up now" button remains a - * fallback for any flow that doesn't route through submitbutton. */ - function setupInterception() { - if (!wrapSubmit()) { - document.addEventListener('DOMContentLoaded', wrapSubmit); - } - } - - function wrapSubmit() { - var J = window.Joomla; - - if (!J || typeof J.submitbutton !== 'function' || J.submitbutton.__msbWrapped) { - return !!(J && J.submitbutton && J.submitbutton.__msbWrapped); - } - - var original = J.submitbutton; - - var wrapped = function (task) { - if (!backedUp && !running && isUpdateTask(task)) { - var args = arguments; - runBackup().then(function (ok) { - if (ok) { - original.apply(window.Joomla, args); - } - }); - - return; - } - - return original.apply(window.Joomla, arguments); - }; - - wrapped.__msbWrapped = true; - J.submitbutton = wrapped; - - return true; - } - - /* Only the actual "apply the update" tasks — never Find Updates / Clear - * Cache (update.find / update.purge) which also start with "update". */ - function isUpdateTask(task) { - return task === 'update.update' || task === 'update.install'; - } - - function injectNotice() { - var host = document.querySelector('main') || document.querySelector('#content') || document.body; - - if (!host || document.getElementById('msb-preupdate-notice')) { - return; - } - - var bar = document.createElement('div'); - bar.id = 'msb-preupdate-notice'; - bar.setAttribute('role', 'status'); - bar.style.cssText = 'display:flex;align-items:center;gap:1rem;flex-wrap:wrap;' - + 'margin:0 0 1rem 0;padding:.75rem 1rem;border:1px solid #f0c36d;' - + 'background:#fcf8e3;border-radius:.35rem;color:#8a6d3b;font-size:.95rem;'; - - var text = document.createElement('span'); - text.style.cssText = 'flex:1 1 auto;'; - text.textContent = L.notice || 'A full-site backup is recommended before you update.'; - - var btn = document.createElement('button'); - btn.type = 'button'; - btn.id = 'msb-preupdate-run'; - btn.className = 'btn btn-warning btn-sm'; - btn.textContent = L.backupNow || 'Back up now'; - btn.addEventListener('click', runBackup); - - var dismiss = document.createElement('button'); - dismiss.type = 'button'; - dismiss.className = 'btn btn-link btn-sm'; - dismiss.style.cssText = 'color:#8a6d3b;'; - dismiss.textContent = L.dismiss || 'Dismiss'; - dismiss.addEventListener('click', function () { bar.remove(); }); - - bar.appendChild(text); - bar.appendChild(btn); - bar.appendChild(dismiss); - host.insertBefore(bar, host.firstChild); - } - - /* ── The live-progress overlay (dependency-free, mirrors the dashboard) ── */ - - function buildOverlay() { - var overlay = document.createElement('div'); - overlay.id = 'msb-preupdate-overlay'; - overlay.style.cssText = 'position:fixed;inset:0;z-index:10000;display:flex;' - + 'align-items:center;justify-content:center;background:rgba(0,0,0,.5);'; - - var box = document.createElement('div'); - box.style.cssText = 'width:min(480px,92vw);background:#fff;border-radius:.5rem;' - + 'padding:1.5rem;box-shadow:0 10px 40px rgba(0,0,0,.3);'; - - var title = document.createElement('h3'); - title.id = 'msb-pu-title'; - title.style.cssText = 'margin:0 0 1rem 0;font-size:1.15rem;'; - title.textContent = L.backingUp || 'Backing up…'; - - var track = document.createElement('div'); - track.style.cssText = 'height:1.25rem;background:#e9ecef;border-radius:.35rem;overflow:hidden;'; - - var barfill = document.createElement('div'); - barfill.id = 'msb-pu-bar'; - barfill.style.cssText = 'height:100%;width:0;background:#198754;transition:width .3s ease;'; - track.appendChild(barfill); - - var status = document.createElement('div'); - status.id = 'msb-pu-status'; - status.style.cssText = 'margin-top:.75rem;font-size:.9rem;color:#555;'; - status.textContent = L.starting || 'Starting backup…'; - - box.appendChild(title); - box.appendChild(track); - box.appendChild(status); - overlay.appendChild(box); - document.body.appendChild(overlay); - - return { overlay: overlay, title: title, bar: barfill, status: status }; - } - - function setProgress(ui, progress, message) { - var pct = Math.max(0, Math.min(100, parseInt(progress, 10) || 0)); - ui.bar.style.width = pct + '%'; - - if (message) { - ui.status.textContent = message; - } - } - - function post(params) { - var body = new URLSearchParams(); - body.append(cfg.token, '1'); - - Object.keys(params).forEach(function (k) { - body.append(k, params[k]); - }); - - return fetch(cfg.ajaxUrl, { - method: 'POST', - body: body, - headers: { 'X-Requested-With': 'XMLHttpRequest' } - }).then(function (r) { return r.json(); }); - } - - async function runBackup() { - if (running) { - return; - } - - running = true; - - var runBtn = document.getElementById('msb-preupdate-run'); - - if (runBtn) { - runBtn.disabled = true; - } - - var ui = buildOverlay(); - var guard = function (e) { e.preventDefault(); e.returnValue = ''; }; - window.addEventListener('beforeunload', guard); - - try { - var init = await post({ task: 'ajax.init', profile_id: cfg.profileId }); - - if (!init || init.error || !init.session_id) { - throw new Error((init && init.message) || 'Could not start backup'); - } - - setProgress(ui, init.progress, init.message); - - var done = false; - - while (!done) { - var step = await post({ task: 'ajax.step', session_id: init.session_id }); - - if (!step || step.error) { - throw new Error((step && step.message) || 'Backup step failed'); - } - - setProgress(ui, step.progress, step.message); - done = step.done || false; - } - - /* Tell the server the pre-update backup is satisfied so the - synchronous onExtensionBeforeUpdate backup is skipped. */ - try { await post({ task: 'ajax.preupdateAck' }); } catch (ignore) {} - - backedUp = true; - ui.title.textContent = L.complete || 'Backup complete — safe to update.'; - setProgress(ui, 100, ''); - - var notice = document.getElementById('msb-preupdate-notice'); - - if (notice) { - notice.style.background = '#d1e7dd'; - notice.style.borderColor = '#badbcc'; - notice.style.color = '#0f5132'; - } - - setTimeout(function () { ui.overlay.remove(); }, 1500); - - return true; - } catch (err) { - ui.title.textContent = (L.failed || 'Backup failed') + ': ' + err.message; - ui.bar.style.background = '#dc3545'; - - if (runBtn) { - runBtn.disabled = false; - } - - return false; - } finally { - running = false; - window.removeEventListener('beforeunload', guard); - } - } -})(); diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 93ebdd58..de1ff531 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -29,10 +29,6 @@ language/en-GB/plg_system_mokosuitebackup.sys.ini - - js - -
diff --git a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php index b2399603..41ae8765 100644 --- a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php +++ b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php @@ -13,11 +13,8 @@ namespace Joomla\Plugin\System\MokoSuiteBackup\Extension; defined('_JEXEC') or die; use Joomla\CMS\Component\ComponentHelper; -use Joomla\CMS\Document\HtmlDocument; use Joomla\CMS\Factory; -use Joomla\CMS\Language\Text; use Joomla\CMS\Plugin\CMSPlugin; -use Joomla\CMS\Router\Route; use Joomla\CMS\Session\Session; use Joomla\CMS\Uri\Uri; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RetentionManager; @@ -34,7 +31,6 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface return [ 'onAfterInitialise' => 'onAfterInitialise', 'onAfterRoute' => 'onAfterRoute', - 'onBeforeCompileHead' => 'onBeforeCompileHead', 'onExtensionBeforeUpdate' => 'onExtensionBeforeUpdate', 'onExtensionBeforeUninstall' => 'onExtensionBeforeUninstall', ]; @@ -127,6 +123,10 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface return; } + // Pre-update: send Joomla core-update installs through the full-screen + // backup page BEFORE the update runs (may not return from here). + $this->maybeRedirectForUpdate(); + if (!(int) $this->params->get('auto_cleanup', 1)) { return; } @@ -146,74 +146,79 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface } /** - * Inject the pre-update backup notice + live-progress modal on the admin - * update pages (com_joomlaupdate and com_installer's update view). + * Send a Joomla core-update install through the full-screen backup page + * BEFORE the update runs (Akeeba-style), so no backup runs synchronously + * inside the update request (which is what white-screened large sites). * - * The server-side onExtensionBeforeUpdate backup runs synchronously and - * cannot drive a browser modal, so this offers an on-demand "Back up now" - * button that runs the same stepped backup as the dashboard and shows live - * progress. On success the client pings ajax.preupdateAck, which arms the - * same throttle key runPreActionBackup() checks — so the subsequent server - * update does not run a duplicate backup. + * Flow: user clicks "Install the update" (com_joomlaupdate&task=update.install) + * → we redirect to com_mokosuitebackup&view=runbackup (full screen) with a + * returnurl back to update.install&is_backed_up=1 → the backup runs on its + * own page with real progress → on completion the browser returns to + * update.install, we arm the throttle so onExtensionBeforeUpdate doesn't + * duplicate the backup, and the update proceeds. */ - public function onBeforeCompileHead(Event $event): void + private function maybeRedirectForUpdate(): void { - $app = $this->getApplication(); - - if (!$app->isClient('administrator')) { - return; - } - - $input = $app->getInput(); - $option = $input->getCmd('option', ''); - $view = $input->getCmd('view', ''); - - $onUpdatePage = $option === 'com_joomlaupdate' - || ($option === 'com_installer' && $view === 'update'); - - if (!$onUpdatePage) { - return; - } - $params = ComponentHelper::getParams('com_mokosuitebackup'); - // Respect the notice toggle and the pre-update backup feature flag. - if (!(int) $params->get('show_update_notice', 1) || !(int) $params->get('backup_before_update', 0)) { + if (!(int) $params->get('backup_before_update', 0)) { return; } - // Already backed up this session (within the throttle window)? Stay quiet. - $lastRun = (int) Factory::getSession()->get('mokosuitebackup.preaction_backup_before_update', 0); + $app = $this->getApplication(); + $input = $app->getInput(); + + if ($input->getCmd('option', '') !== 'com_joomlaupdate' + || $input->getCmd('task', '') !== 'update.install') { + return; + } + + $session = Factory::getSession(); + + // Returned from the backup screen: arm the throttle so the synchronous + // onExtensionBeforeUpdate backup doesn't run again, then let it proceed. + if ((int) $input->getInt('is_backed_up', 0) === 1) { + $session->set('mokosuitebackup.preaction_backup_before_update', time()); + + return; + } + + // Backed up recently already — let the update proceed. + $lastRun = (int) $session->get('mokosuitebackup.preaction_backup_before_update', 0); if ($lastRun > 0 && (time() - $lastRun) < 600) { return; } - $doc = $app->getDocument(); + // Super Users only. + $user = $app->getIdentity(); - if (!$doc instanceof HtmlDocument) { + if (!$user || $user->guest || !$user->authorise('core.admin')) { return; } - $doc->getWebAssetManager()->useScript('core'); + $token = Session::getFormToken(); + $profileId = (int) $params->get('default_profile', 1); - $doc->addScriptOptions('plg_system_mokosuitebackup.preupdate', [ - 'ajaxUrl' => Route::_('index.php?option=com_mokosuitebackup&format=json', false), - 'token' => Session::getFormToken(), - 'profileId' => (int) $params->get('default_profile', 1), - 'autoIntercept' => (bool) (int) $params->get('preupdate_auto_intercept', 0), - 'labels' => [ - 'notice' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_UPDATE_NOTICE'), - 'backupNow' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_NOW'), - 'dismiss' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_DISMISS'), - 'backingUp' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKING_UP'), - 'starting' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_STARTING'), - 'complete' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_COMPLETE'), - 'failed' => Text::_('PLG_SYSTEM_MOKOJOOMBACKUP_BACKUP_FAILED'), - ], - ]); + // Where to send the browser after the backup: back to the update install. + $returnUri = new Uri(Uri::base() . 'index.php'); + $returnUri->setVar('option', 'com_joomlaupdate'); + $returnUri->setVar('task', 'update.install'); + $returnUri->setVar('is_backed_up', '1'); + $returnUri->setVar($token, '1'); - $doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/update-backup.js', [], ['defer' => true]); + // The full-screen backup page (chromeless via tmpl=component). + $redirect = new Uri(Uri::base() . 'index.php'); + $redirect->setVar('option', 'com_mokosuitebackup'); + $redirect->setVar('view', 'runbackup'); + $redirect->setVar('tmpl', 'component'); + $redirect->setVar('autostart', '1'); + $redirect->setVar('profile_id', (string) $profileId); + $redirect->setVar('description', 'Pre-update backup'); + $redirect->setVar('returnurl', base64_encode($returnUri->toString())); + $redirect->setVar($token, '1'); + + $app->redirect($redirect->toString()); } /** From 4311ec8f99b2d75c1fd2366e61e2c9b8943aec06 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 22:57:40 -0500 Subject: [PATCH 22/51] fix(security): enforce scheme allow-list on runbackup return URL The pre-update return URL is assigned to window.location.href, so a javascript:/data: URI (empty host) would have passed the host-only check and executed. Require an absolute http(s) same-host URL or a single-slash root-relative path; reject javascript:/data:/vbscript: and protocol-relative //host. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- .../tmpl/runbackup/default.php | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php index d4cceddd..dab4defb 100644 --- a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php +++ b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php @@ -22,23 +22,36 @@ use Joomla\CMS\Uri\Uri; $ajaxToken = Session::getFormToken(); $ajaxUrl = Route::_('index.php?option=com_mokosuitebackup&format=json', false); -/* Validate the return URL to prevent an open redirect: accept only a relative - URL or one whose host matches this site. Falls back to '' (no redirect). */ +/* Validate the return URL to prevent an open redirect / javascript: XSS + (the value ends up in window.location.href). Accept ONLY: + - an absolute http(s) URL whose host matches this site, or + - a root-relative path starting with a single "/" (not "//"). + This rejects javascript:, data:, vbscript: (empty host) and + protocol-relative //evil.com. Falls back to '' (no redirect). */ $safeReturnUrl = ''; if ($this->returnUrl !== '') { $decoded = base64_decode($this->returnUrl, true); $raw = ($decoded !== false && $decoded !== '') ? $decoded : $this->returnUrl; - try { - $rootHost = Uri::getInstance(Uri::root())->getHost(); - $target = new Uri($raw); + $isAbsoluteHttp = (bool) preg_match('#^https?://#i', $raw); + $isRootRelative = isset($raw[0]) && $raw[0] === '/' && (!isset($raw[1]) || $raw[1] !== '/'); - if ($target->getHost() === '' || strcasecmp($target->getHost(), $rootHost) === 0) { - $safeReturnUrl = $raw; + if ($isAbsoluteHttp || $isRootRelative) { + try { + $rootHost = Uri::getInstance(Uri::root())->getHost(); + $target = new Uri($raw); + $scheme = strtolower((string) $target->getScheme()); + + $schemeOk = $scheme === '' || $scheme === 'http' || $scheme === 'https'; + $hostOk = $target->getHost() === '' || strcasecmp($target->getHost(), $rootHost) === 0; + + if ($schemeOk && $hostOk) { + $safeReturnUrl = $raw; + } + } catch (\Throwable $e) { + $safeReturnUrl = ''; } - } catch (\Throwable $e) { - $safeReturnUrl = ''; } } From 0d832e60e94c6499629d46c4d5eaa5230d073eab Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 03:58:23 +0000 Subject: [PATCH 23/51] chore(version): pre-release bump to 02.58.14-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/MokoSuiteClient | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.14.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 14 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.14.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 087c7c15..3a22db58 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.13 +# VERSION: 02.58.14 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 41652fd4..e892093c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.13 +VERSION: 02.58.14 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/MokoSuiteClient b/source/packages/MokoSuiteClient index 30a6b532..ca71cdd0 160000 --- a/source/packages/MokoSuiteClient +++ b/source/packages/MokoSuiteClient @@ -1 +1 @@ -Subproject commit 30a6b53222d9d8980134aeb4cb51f29579d8f9f1 +Subproject commit ca71cdd016ec7a9fdba41429111757a5d49be1b9 diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 740cb258..1fca887f 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.13 + 02.58.14 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.14.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.14.sql new file mode 100644 index 00000000..6c3a5263 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.14.sql @@ -0,0 +1 @@ +/* 02.58.14 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 67c51dab..ccd2d3f2 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.13 + 02.58.14 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 80cd9113..5f472d70 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.13 + 02.58.14 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 10d438da..c25a3baa 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.13 + 02.58.14 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 1d346801..8a8283be 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.13 + 02.58.14 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index cd89f6e1..7547ab8e 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.13 + 02.58.14 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 8cb06864..1a3e1f39 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.13 + 02.58.14 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 440ae4b4..180126c8 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.13 + 02.58.14 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 09537509..dc4681b6 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.13 + 02.58.14 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index b2c5c2b4..98f0fdbe 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.13 + 02.58.14 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 6b4f98869a2dc04c59f30ddf09224c40c9903135 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 6 Jul 2026 10:49:49 -0500 Subject: [PATCH 24/51] fix: installer postflight honest about partial/failed install Joomla logs a failed child extension but still runs the package postflight, so the 'installed successfully' message could appear on a broken install. In postflight, before the success/next-steps messages: - verify every bundled child declared in the package manifest actually registered in #__extensions (match element+type, and folder=group for plugins), and - verify the component's schema tables (parsed dynamically from the installed install.mysql.sql) exist in the DB. If anything is missing, enqueue an error listing it and RETURN before the success message. Fail-open (try/catch -> nothing missing) so a transient manifest/DB/IO glitch can't fail a good install. Unconditional housekeeping (download-key restore, menu fixes) still runs regardless. Names escaped with htmlspecialchars; table/child lists derived dynamically, never hard-coded. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 1 + source/script.php | 156 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f41d813c..94209868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - The backend controller, Web Services API controller, and legacy `cli/mokosuitebackup.php` now run backups through the shared `BackupRunner` (gaining the normalized complete/warning/fail status) instead of instantiating `BackupEngine` directly. ### Fixed +- Package installer is now **honest about success**: the postflight no longer prints "installed successfully" / next-steps when the install actually failed or only partially completed. Joomla logs a failed child extension but still runs the package postflight, so the postflight now (a) verifies every bundled child declared in the manifest actually registered in `#__extensions` (matched by element + type, and folder/group for plugins) and (b) verifies the component's schema tables — derived dynamically from the installed `install.mysql.sql` — actually exist. If anything is missing it enqueues an error listing what's missing and stops before the success message. Fail-open: any manifest/DB/IO glitch is treated as "nothing missing" so a transient error never turns a good install into a false failure. Unconditional housekeeping (e.g. download-key restore) still runs regardless. - Pre-update/uninstall backup no longer **white-screens** the update on large sites. The synchronous backup that runs inside the extension update/uninstall request now raises `max_execution_time`/`memory_limit` (and `ignore_user_abort`) like the web-cron path, so it can't exhaust the request's default limits mid-backup. (Core Joomla updates additionally get a full-screen backup screen — see below.) - Archive names for **CLI/console-triggered backups** no longer come out as `joomla.invalid_…`. The `[HOST]` placeholder took `$_SERVER['HTTP_HOST']` verbatim, but Joomla's console fills that with the reserved sentinel host `joomla.invalid`; the resolver now treats that (like empty/`localhost`) as unusable and falls back to the configured `live_site` host, then the system hostname. (Set the site's *live_site* to get the exact domain in CLI-built names.) - Standalone restore script generation no longer aborts backups with `str_replace() expects at least 3 arguments, 2 given`. `MokoRestore::generateStandaloneScript()` had a `str_replace()` call (the "Backup Archive" pre-check rewrite) that was missing its `$php` subject argument, so **every** standalone-mode backup fatally errored while "Generating standalone restore.php…" — the archive still finalized and uploaded, but no `restore.php` was ever produced (the true root cause behind #226). (#226) diff --git a/source/script.php b/source/script.php index 65ade9fe..b2f64169 100644 --- a/source/script.php +++ b/source/script.php @@ -188,6 +188,35 @@ class Pkg_MokoSuiteBackupInstallerScript PREPARE/EXECUTE migration that MySQL 8 rejected in Joomla's installer). */ $this->dropLegacyRemoteColumns(); + /* Honesty gate: Joomla logs a failed child extension but still runs the + package postflight, so verify every bundled child actually registered + before claiming success. If any are missing, say so and do NOT print the + success / next-steps messages. (All housekeeping above runs regardless.) + Fail-open — a manifest/DB glitch returns no missing children. */ + $problems = $this->findMissingPackageChildren($parent); + + foreach ($this->findMissingComponentTables() as $table) { + $problems[] = 'table ' . $table; + } + + if (!empty($problems)) { + $safe = array_map( + static fn($m) => htmlspecialchars($m, ENT_QUOTES, 'UTF-8'), + $problems + ); + $detail = count($safe) > 3 + ? count($safe) . ' bundled extensions/tables are missing' + : 'missing: ' . implode(', ', $safe); + + Factory::getApplication()->enqueueMessage( + 'MokoSuiteBackup did not install correctly — ' . $detail + . '. Review the installation log and re-install.', + 'error' + ); + + return; + } + /* Install completion notice (install and update) */ $this->installSuccessful(); @@ -208,6 +237,133 @@ class Pkg_MokoSuiteBackupInstallerScript } } + /** + * Verify every bundled child extension declared in the package manifest + * actually registered in #__extensions. Joomla logs a failed child but does + * not fail the package, so this answers "did the package really install?" + * honestly. Returns human-readable labels for any missing children (empty = + * all present). Matching mirrors extension uniqueness: element + type, and + * for plugins also folder (group), since plugins are only unique that way. + * + * Fail-open: any manifest/DB error returns [] so a transient glitch can never + * turn a good install into a false failure. + * + * @param InstallerAdapter $parent Package installer adapter + * + * @return string[] Labels of missing children (empty when everything installed) + */ + private function findMissingPackageChildren(InstallerAdapter $parent): array + { + try { + $manifest = $parent->getParent()->getManifest(); + + if (!$manifest instanceof \SimpleXMLElement + || !isset($manifest->files) || !isset($manifest->files->file)) { + return []; + } + + $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class); + $missing = []; + + foreach ($manifest->files->file as $file) { + $attrs = $file->attributes(); + $element = isset($attrs['id']) ? (string) $attrs['id'] : ''; + $extType = isset($attrs['type']) ? (string) $attrs['type'] : ''; + + if ($element === '' || $extType === '') { + continue; + } + + $query = $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . ' = ' . $db->quote($element)) + ->where($db->quoteName('type') . ' = ' . $db->quote($extType)); + + $group = ''; + + /* Plugins are only unique by element + folder (group). */ + if ($extType === 'plugin' && isset($attrs['group'])) { + $group = (string) $attrs['group']; + + if ($group !== '') { + $query->where($db->quoteName('folder') . ' = ' . $db->quote($group)); + } + } + + $db->setQuery($query); + + if ((int) $db->loadResult() === 0) { + $label = $extType . ' "' . $element . '"'; + + if ($group !== '') { + $label .= ' (' . $group . ')'; + } + + $missing[] = $label; + } + } + + return $missing; + } catch (\Throwable $e) { + return []; + } + } + + /** + * Verify the component's schema actually installed. The component has no + * installer script of its own, so this package postflight is the only place + * to catch a component whose extension row exists but whose CREATE TABLE + * statements failed (e.g. an installer-rejected migration). Reads the + * *installed* install SQL, extracts every declared table, and confirms each + * exists. Table names are derived dynamically — nothing is hard-coded. + * + * Fail-open: any IO/DB/parse error returns [] so a glitch can't fail a good + * install. + * + * @return string[] Missing table names (empty when all present) + */ + private function findMissingComponentTables(): array + { + try { + $sqlFile = JPATH_ADMINISTRATOR . '/components/com_mokosuitebackup/sql/install.mysql.sql'; + + if (!is_file($sqlFile)) { + return []; + } + + $sql = file_get_contents($sqlFile); + + if ($sql === false || $sql === '') { + return []; + } + + if (!preg_match_all('/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(#__[A-Za-z0-9_]+)/i', $sql, $m)) { + return []; + } + + $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class); + $prefix = $db->getPrefix(); + + /* Existing tables, compared case-insensitively. */ + $existing = array_flip(array_map('strtolower', $db->getTableList())); + + $missing = []; + + foreach (array_unique($m[1]) as $declared) { + $real = str_replace('#__', $prefix, $declared); + + if (!isset($existing[strtolower($real)])) { + $missing[] = $real; + } + } + + return $missing; + } catch (\Throwable $e) { + return []; + } + } + /** * Remove the orphaned component registration created when the component * briefly used the "Type - Name" display convention. Joomla derives a From 38160e9ae9987b5d24a830e7abc37620ef3a8a52 Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Mon, 6 Jul 2026 15:50:17 +0000 Subject: [PATCH 25/51] chore(version): pre-release bump to 02.58.15-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.15.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.15.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 3a22db58..a600541c 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.14 +# VERSION: 02.58.15 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index e892093c..c803c891 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.14 +VERSION: 02.58.15 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 1fca887f..60b7b280 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.14 + 02.58.15 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.15.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.15.sql new file mode 100644 index 00000000..e0816695 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.15.sql @@ -0,0 +1 @@ +/* 02.58.15 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index ccd2d3f2..f9e6dc80 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.14 + 02.58.15 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 5f472d70..1ba0bcfb 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.14 + 02.58.15 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index c25a3baa..c5e703bc 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.14 + 02.58.15 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 8a8283be..0e03a44a 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.14 + 02.58.15 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 7547ab8e..87ffe551 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.14 + 02.58.15 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 1a3e1f39..2b76666b 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.14 + 02.58.15 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 180126c8..004adea6 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.14 + 02.58.15 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index dc4681b6..446e8ce2 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.14 + 02.58.15 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 98f0fdbe..f72df232 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.14 + 02.58.15 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 132701035f46e6ddba98a4f2b1ff63a164cdb25a Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 7 Jul 2026 08:40:33 -0500 Subject: [PATCH 26/51] chore(repo): refine .gitignore rules and update MokoSuiteClient submodule --- .gitignore | 14 ++++++++++++-- source/packages/MokoSuiteClient | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 4881dc19..1f90377a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ secrets/ *.pid *.seed + # ============================================================ # OS / Editor / IDE cruft # ============================================================ @@ -45,6 +46,7 @@ Icon? .idea/ .settings/ .claude/ +.gemini/ .vscode/* !.vscode/tasks.json !.vscode/settings.json.example @@ -116,6 +118,8 @@ out/ *.map *.css.map *.js.map +*.min.css +*.min.js *.tsbuildinfo # ============================================================ @@ -151,7 +155,7 @@ package-lock.json # PHP / Composer tooling # ============================================================ vendor/ -!source/media/vendor/ +!src/media/vendor/ composer.lock *.phar codeception.phar @@ -177,7 +181,7 @@ __pycache__/ *.egg *.egg-info/ .installed.cfg -MANIFEST +/MANIFEST develop-eggs/ downloads/ eggs/ @@ -201,3 +205,9 @@ hypothesis/ profile.ps1 .mcp.json + +# ============================================================ +# Local wiki clone (not version controlled) +# ============================================================ +wiki/ +docs/ diff --git a/source/packages/MokoSuiteClient b/source/packages/MokoSuiteClient index ca71cdd0..9df6bea4 160000 --- a/source/packages/MokoSuiteClient +++ b/source/packages/MokoSuiteClient @@ -1 +1 @@ -Subproject commit ca71cdd016ec7a9fdba41429111757a5d49be1b9 +Subproject commit 9df6bea4b7480b2e443898ad84a279070ba4a7f6 From 482e876932535e9912ac46ad8c8f1df55fe04a60 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Tue, 7 Jul 2026 14:10:17 +0000 Subject: [PATCH 27/51] chore(version): auto-bump patch 02.58.16-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.16.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.16.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index a600541c..49e104b5 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.15 +# VERSION: 02.58.16 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index c803c891..8a4c47eb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.15 +VERSION: 02.58.16 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 60b7b280..75d7d19a 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.15 + 02.58.16 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.16.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.16.sql new file mode 100644 index 00000000..70c3ae57 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.16.sql @@ -0,0 +1 @@ +/* 02.58.16 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index f9e6dc80..0a381112 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.15 + 02.58.16 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 1ba0bcfb..f3cbdb3a 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.15 + 02.58.16 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index c5e703bc..11c5cc9e 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.15 + 02.58.16 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 0e03a44a..7b0998bf 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.15 + 02.58.16 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 87ffe551..a6179740 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.15 + 02.58.16 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 2b76666b..29463244 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.15 + 02.58.16 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 004adea6..ce9ae25a 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.15 + 02.58.16 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 446e8ce2..472e10a8 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.15 + 02.58.16 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index f72df232..73bc6e7a 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.15 + 02.58.16 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 5574e8569af0ca424ed11c889ba0509668ebf2cd Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Tue, 7 Jul 2026 14:10:28 +0000 Subject: [PATCH 28/51] chore(version): pre-release bump to 02.58.17-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/MokoSuiteClient | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.17.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 14 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.17.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 49e104b5..cc754b16 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.16 +# VERSION: 02.58.17 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 8a4c47eb..7e1087cb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.16 +VERSION: 02.58.17 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/MokoSuiteClient b/source/packages/MokoSuiteClient index 9df6bea4..246f7bb1 160000 --- a/source/packages/MokoSuiteClient +++ b/source/packages/MokoSuiteClient @@ -1 +1 @@ -Subproject commit 9df6bea4b7480b2e443898ad84a279070ba4a7f6 +Subproject commit 246f7bb10c2011ec667468302cff67d5882c4456 diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 75d7d19a..233d8464 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.16 + 02.58.17 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.17.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.17.sql new file mode 100644 index 00000000..3cdb9169 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.17.sql @@ -0,0 +1 @@ +/* 02.58.17 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 0a381112..98be043e 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.16 + 02.58.17 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index f3cbdb3a..14ac64c0 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.16 + 02.58.17 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 11c5cc9e..230da13f 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.16 + 02.58.17 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 7b0998bf..99402c16 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.16 + 02.58.17 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index a6179740..056d88da 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.16 + 02.58.17 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 29463244..fb4be78e 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.16 + 02.58.17 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index ce9ae25a..8db1a5dd 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.16 + 02.58.17 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 472e10a8..7578fe4b 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.16 + 02.58.17 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 73bc6e7a..3e355230 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.16 + 02.58.17 2026-06-02 Moko Consulting hello@mokoconsulting.tech From e56a1fd8f6222f7cb9d90d20baad7c386670cbfa Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 9 Jul 2026 18:10:01 -0500 Subject: [PATCH 29/51] fix: pre-update full-screen backup fires on Joomla 4/5/6 (view=update) The redirect only matched task=update.install, which Joomla 4/5/6 do not use. Their 'Install the update' flow server-side-redirects to the updating page (view=update), which then extracts the downloaded package from JavaScript (AJAX to com_joomlaupdate/extract.php). So the view=update page LOAD is the last server-side moment before any files change. Intercept that page load (in onAfterRoute, before com_joomlaupdate renders it), redirect to the full-screen backup with a returnurl back to view=update&is_backed_up=1, then the browser returns and the JS extraction runs. update.finalise is deliberately NOT intercepted -- by then the files are already extracted, too late for a pre-update backup. Legacy task=update.install (Joomla 3) still handled. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 1 + .../src/Extension/MokoSuiteBackup.php | 43 +++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94209868..79a998ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixed - Package installer is now **honest about success**: the postflight no longer prints "installed successfully" / next-steps when the install actually failed or only partially completed. Joomla logs a failed child extension but still runs the package postflight, so the postflight now (a) verifies every bundled child declared in the manifest actually registered in `#__extensions` (matched by element + type, and folder/group for plugins) and (b) verifies the component's schema tables — derived dynamically from the installed `install.mysql.sql` — actually exist. If anything is missing it enqueues an error listing what's missing and stops before the success message. Fail-open: any manifest/DB/IO glitch is treated as "nothing missing" so a transient error never turns a good install into a false failure. Unconditional housekeeping (e.g. download-key restore) still runs regardless. +- Pre-update full-screen backup screen now actually triggers on **Joomla 6** (and 4/5). The redirect matched only the legacy `update.install` task, which Joomla 4/5/6 don't use — they server-side-redirect to the **updating page `view=update`**, which then extracts the downloaded package from JavaScript. The plugin now intercepts the `view=update` page **load** (the last point before any files change) and returns the browser there flagged `is_backed_up=1` so the extraction proceeds after the backup. (`update.finalise` is intentionally not intercepted — by then the files are already extracted.) - Pre-update/uninstall backup no longer **white-screens** the update on large sites. The synchronous backup that runs inside the extension update/uninstall request now raises `max_execution_time`/`memory_limit` (and `ignore_user_abort`) like the web-cron path, so it can't exhaust the request's default limits mid-backup. (Core Joomla updates additionally get a full-screen backup screen — see below.) - Archive names for **CLI/console-triggered backups** no longer come out as `joomla.invalid_…`. The `[HOST]` placeholder took `$_SERVER['HTTP_HOST']` verbatim, but Joomla's console fills that with the reserved sentinel host `joomla.invalid`; the resolver now treats that (like empty/`localhost`) as unusable and falls back to the configured `live_site` host, then the system hostname. (Set the site's *live_site* to get the exact domain in CLI-built names.) - Standalone restore script generation no longer aborts backups with `str_replace() expects at least 3 arguments, 2 given`. `MokoRestore::generateStandaloneScript()` had a `str_replace()` call (the "Backup Archive" pre-check rewrite) that was missing its `$php` subject argument, so **every** standalone-mode backup fatally errored while "Generating standalone restore.php…" — the archive still finalized and uploaded, but no `restore.php` was ever produced (the true root cause behind #226). (#226) diff --git a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php index 41ae8765..a7babc7c 100644 --- a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php +++ b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php @@ -146,16 +146,23 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface } /** - * Send a Joomla core-update install through the full-screen backup page - * BEFORE the update runs (Akeeba-style), so no backup runs synchronously - * inside the update request (which is what white-screened large sites). + * Send a Joomla core update through the full-screen backup page BEFORE the + * update applies (Akeeba-style), so no backup runs synchronously inside the + * update request (which is what white-screened large sites). * - * Flow: user clicks "Install the update" (com_joomlaupdate&task=update.install) - * → we redirect to com_mokosuitebackup&view=runbackup (full screen) with a - * returnurl back to update.install&is_backed_up=1 → the backup runs on its - * own page with real progress → on completion the browser returns to - * update.install, we arm the throttle so onExtensionBeforeUpdate doesn't - * duplicate the backup, and the update proceeds. + * Interception point by Joomla version: on Joomla 4/5/6 the "Install the + * update" flow server-side-redirects to the updating page `view=update`, + * which *then* runs the file extraction from JavaScript (an AJAX call to + * com_joomlaupdate/extract.php). So the `view=update` page LOAD is the last + * moment before any files change — we intercept that. (Older releases used + * `task=update.install`.) In onAfterRoute — before com_joomlaupdate renders + * the page — we redirect to view=runbackup with a returnurl back to the same + * page flagged `is_backed_up=1`; the backup runs on its own full-screen page, + * then the browser returns to `view=update`, whose JS extraction then runs. + * On return we arm the throttle so onExtensionBeforeUpdate won't duplicate it. + * + * Note: `update.finalise` is deliberately NOT intercepted — by then the files + * are already extracted, so it is too late for a pre-update backup. */ private function maybeRedirectForUpdate(): void { @@ -167,9 +174,13 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface $app = $this->getApplication(); $input = $app->getInput(); + $view = $input->getCmd('view', ''); + $task = $input->getCmd('task', ''); + // Joomla 4/5/6 updating page (before its JS extracts), or the legacy + // Joomla 3 install task. if ($input->getCmd('option', '') !== 'com_joomlaupdate' - || $input->getCmd('task', '') !== 'update.install') { + || ($view !== 'update' && $task !== 'update.install')) { return; } @@ -200,10 +211,18 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface $token = Session::getFormToken(); $profileId = (int) $params->get('default_profile', 1); - // Where to send the browser after the backup: back to the update install. + // Where to send the browser after the backup: back to the same update + // entry point that fired (the updating page, or the legacy install task), + // flagged so this doesn't loop. $returnUri = new Uri(Uri::base() . 'index.php'); $returnUri->setVar('option', 'com_joomlaupdate'); - $returnUri->setVar('task', 'update.install'); + + if ($view === 'update') { + $returnUri->setVar('view', 'update'); + } else { + $returnUri->setVar('task', $task); + } + $returnUri->setVar('is_backed_up', '1'); $returnUri->setVar($token, '1'); From a63b87d97c9f403321833b49067974c1d216ac68 Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Thu, 9 Jul 2026 23:23:12 +0000 Subject: [PATCH 30/51] chore(version): pre-release bump to 02.58.19-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.19.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.19.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index cc754b16..f70a189d 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.17 +# VERSION: 02.58.19 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 7e1087cb..9e0aa406 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.17 +VERSION: 02.58.19 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 233d8464..3d15dc7e 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.17 + 02.58.19 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.19.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.19.sql new file mode 100644 index 00000000..ef0ad5d8 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.19.sql @@ -0,0 +1 @@ +/* 02.58.19 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 98be043e..24a0764a 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.17 + 02.58.19 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 14ac64c0..5c43ab09 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.17 + 02.58.19 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 230da13f..edd7e98b 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.17 + 02.58.19 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 99402c16..e95fb7f9 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.17 + 02.58.19 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 056d88da..3bca7be4 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.17 + 02.58.19 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index fb4be78e..518ae801 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.17 + 02.58.19 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 8db1a5dd..d6f0b141 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.17 + 02.58.19 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 7578fe4b..a80405c6 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.17 + 02.58.19 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 3e355230..f6b972f0 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.17 + 02.58.19 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 7a0636606e3e33833909e0c0920380f759c9e506 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Fri, 10 Jul 2026 10:52:59 -0500 Subject: [PATCH 31/51] feat: full-screen backup for extension update/uninstall + view-record button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the full-screen backup screen beyond core Joomla updates: - Extension updates/uninstalls (com_installer): update.update and manage.remove are POST + checkToken with a checked cid[] list, so a server-side GET redirect can't resume them. New client-side interceptor (installer-backup.js, injected via onBeforeCompileHead on the update and manage views) wraps Joomla.submitbutton, stores the selection, sends the browser to view=runbackup, and on return restores the selection and re-submits the real POST form. Gated by backup_before_update / backup_before_uninstall, super-user only, skipped within the throttle window. - runbackup: on manual 'Backup Now' completion, show a 'View backup record' button (links to view=backup&id=); not shown for the pre-update/uninstall flow (which hands back to Joomla). SteppedBackup Engine now returns record_id in its step result. - runbackup arms the pre-action throttles on success (ajax.preupdateAck, now arming both update+uninstall keys) so the following server-side onExtensionBefore(Update|Uninstall) backup is skipped (no duplicate). - Plugin manifest re-registers media/js. NOTE: untested end-to-end (no dev build available this session) — verify the com_installer resume flow on a dev build before relying on it. Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 2 + .../language/en-GB/com_mokosuitebackup.ini | 1 + .../language/en-US/com_mokosuitebackup.ini | 1 + .../src/Controller/AjaxController.php | 21 ++- .../src/Engine/SteppedBackupEngine.php | 2 + .../tmpl/runbackup/default.php | 25 ++- .../media/js/installer-backup.js | 142 ++++++++++++++++++ .../mokosuitebackup.xml | 4 + .../src/Extension/MokoSuiteBackup.php | 81 ++++++++++ 9 files changed, 269 insertions(+), 10 deletions(-) create mode 100644 source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a998ae..4fec5ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [Unreleased] ### Added +- Full-screen backup now also fronts **extension updates and uninstalls** (Extensions → Update / Manage), not just core Joomla updates. Because `com_installer`'s `update.update` / `manage.remove` are POST actions (CSRF token + a checked `cid[]` list) that a server-side redirect can't cleanly resume, this is done client-side: the toolbar Update/Uninstall click is intercepted, the selection is captured, the full-screen backup runs, and on return the original selection is restored and the real POST form is re-submitted. Gated by `backup_before_update` / `backup_before_uninstall`, super-user only, and skipped if a backup already ran this session. +- Manual **"Backup Now"** completion now offers a **View backup record** button (links straight to the record that was just created). It appears only for manual backups — the pre-update / pre-uninstall flow hands control back to Joomla instead. - **Full-screen backup screen** (`view=runbackup`) for both pre-update and manual backups, modelled on Akeeba's Backup-on-Update — replaces the earlier popup-modal approach. Clicking Joomla's **Install the update** now redirects (server-side, Akeeba-style) to a dedicated full-page backup screen that runs the stepped backup with a real progress bar and then **automatically continues the update** via a validated `returnurl` (flagged `is_backed_up=1` so the backup isn't repeated). Crucially, **no backup runs synchronously inside the update request** — which is what white-screened large sites. The dashboard **Backup Now** now opens the same full-screen screen instead of an inline modal. (#196) - Retention now prunes **remote** copies too: when a backup is pruned by age/count, its archive is deleted from every enabled remote destination (SFTP / FTP / S3 / Google Drive), not just the local copy. Each uploader gained an idempotent `delete()` method (already-absent file = success), and removal is best-effort — a failing destination is logged but never blocks local pruning. The shared standalone `restore.php` is intentionally left in place (every backup overwrites it, so newer backups still depend on it). (#229) diff --git a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini index b6793949..d6cb00b8 100644 --- a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini +++ b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini @@ -347,6 +347,7 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED="Backup failed" COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY="Retry backup" COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup" COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard" +COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record" COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it." COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults" diff --git a/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini index 7789c61e..d344d799 100644 --- a/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini +++ b/source/packages/com_mokosuitebackup/language/en-US/com_mokosuitebackup.ini @@ -69,6 +69,7 @@ COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED="Backup failed" COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY="Retry backup" COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup" COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard" +COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record" COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it." COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults" diff --git a/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php b/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php index cda1f601..0442b4bb 100644 --- a/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php +++ b/source/packages/com_mokosuitebackup/src/Controller/AjaxController.php @@ -85,12 +85,14 @@ class AjaxController extends BaseController } /** - * Mark the pre-update backup as satisfied for this session. + * Mark the pre-action backup as satisfied for this session. * - * Called by the update-page modal after a successful on-demand backup so the - * imminent server-side onExtensionBeforeUpdate backup is skipped — it sets - * the same 10-minute throttle key the system plugin checks, preventing a - * duplicate backup when the admin then clicks Joomla's Update button. + * Called by the full-screen backup screen after a successful backup so the + * imminent server-side onExtensionBeforeUpdate / onExtensionBeforeUninstall + * backup is skipped — it arms the same 10-minute throttle keys the system + * plugin checks, preventing a duplicate backup when the update / uninstall + * then proceeds. Arms BOTH keys so a single ack covers whichever action the + * full-screen backup was fronting. * POST: task=ajax.preupdateAck */ public function preupdateAck(): void @@ -107,10 +109,13 @@ class AjaxController extends BaseController return; } - // Same key + semantics as plg_system_mokosuitebackup::runPreActionBackup(). - Factory::getSession()->set('mokosuitebackup.preaction_backup_before_update', time()); + // Same keys + semantics as plg_system_mokosuitebackup::runPreActionBackup(). + $session = Factory::getSession(); + $now = time(); + $session->set('mokosuitebackup.preaction_backup_before_update', $now); + $session->set('mokosuitebackup.preaction_backup_before_uninstall', $now); - $this->sendJson(['error' => false, 'message' => 'Pre-update backup acknowledged']); + $this->sendJson(['error' => false, 'message' => 'Pre-action backup acknowledged']); } /** diff --git a/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php b/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php index 811a8464..6ced4f5a 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php @@ -217,6 +217,7 @@ class SteppedBackupEngine 'progress' => 100, 'message' => 'Backup complete: ' . $session->archiveName, 'done' => true, + 'record_id' => $session->recordId, ]; } @@ -228,6 +229,7 @@ class SteppedBackupEngine 'progress' => $session->getProgress(), 'message' => $session->statusMessage, 'done' => $session->phase === 'complete', + 'record_id' => $session->recordId, ]; } catch (\Throwable $e) { $session->log('FATAL: ' . $e->getMessage()); diff --git a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php index dab4defb..0faeb92d 100644 --- a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php +++ b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php @@ -64,6 +64,7 @@ $config = [ 'description' => $this->description, 'returnUrl' => $safeReturnUrl, 'dashboardUrl' => $dashboardUrl, + 'recordUrl' => Route::_('index.php?option=com_mokosuitebackup&view=backup&id=__MSBID__', false), 'autostart' => $this->autostart, 'labels' => [ 'starting' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), @@ -74,6 +75,7 @@ $config = [ 'retry' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY'), 'continue' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY'), 'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'), + 'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'), 'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'), ], ]; @@ -105,6 +107,7 @@ $config = [
+
@@ -119,6 +122,7 @@ $config = [ var CFG = ; var L = CFG.labels || {}; var running = false; + var lastRecordId = 0; var el = { bar: document.getElementById('msb-rb-bar'), @@ -127,6 +131,7 @@ $config = [ title: document.getElementById('msb-rb-title'), actions: document.getElementById('msb-rb-actions'), retry: document.getElementById('msb-rb-retry'), + record: document.getElementById('msb-rb-record'), continue: document.getElementById('msb-rb-continue'), dashboard: document.getElementById('msb-rb-dashboard') }; @@ -161,18 +166,32 @@ $config = [ node.classList.remove('d-none'); } - function onComplete() { + async function onComplete() { el.bar.classList.remove('progress-bar-striped', 'progress-bar-animated'); el.bar.classList.add('bg-success'); setBar(100, false); el.title.textContent = L.complete || 'Backup complete'; + /* Arm the pre-action throttles so a following server-side onExtension + Before(Update|Uninstall) backup is skipped (no duplicate backup). + Awaited so it lands before we hand back to the update/uninstall. */ + try { await post({ task: 'ajax.preupdateAck' }); } catch (e) {} + if (CFG.returnUrl) { + /* Pre-update / pre-uninstall flow: continue the original action. + No "view record" button here — we're handing back to Joomla. */ setStatus(L.continuing || 'Continuing…'); window.setTimeout(function () { window.location.href = CFG.returnUrl; }, 1200); } else { + /* Manual "Backup Now": offer to view the record that was just made. */ setStatus(L.complete || 'Backup complete'); showActions(); + + if (lastRecordId > 0 && CFG.recordUrl) { + showBtn(el.record, L.viewRecord || 'View backup record', + CFG.recordUrl.replace('__MSBID__', String(lastRecordId))); + } + showBtn(el.dashboard, L.dashboard || 'Back to dashboard', CFG.dashboardUrl); } } @@ -219,6 +238,7 @@ $config = [ setBar(init.progress, true); setPhase(init.phase || ''); setStatus(init.message || (L.running || 'Backing up…')); + if (init.record_id) { lastRecordId = init.record_id; } var done = false; while (!done) { @@ -231,11 +251,12 @@ $config = [ setBar(step.progress, true); setPhase(step.phase || ''); setStatus(step.message || ''); + if (step.record_id) { lastRecordId = step.record_id; } done = step.done || false; } running = false; - onComplete(); + await onComplete(); } catch (err) { onError(err && err.message ? err.message : String(err)); } diff --git a/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js b/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js new file mode 100644 index 00000000..1153e59a --- /dev/null +++ b/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js @@ -0,0 +1,142 @@ +/** + * MokoSuiteBackup — full-screen backup before an EXTENSION update / uninstall. + * + * Injected by plg_system_mokosuitebackup on the Extensions → Update and + * Extensions → Manage pages. Unlike the core Joomla update (which has a + * server-side page load we can redirect), com_installer's update.update and + * manage.remove are POST actions with a CSRF token and a checked cid[] list — + * so we intercept CLIENT-SIDE: capture the selection, go to the full-screen + * backup page, and on return restore the selection and re-submit the real POST + * form (fresh token) so the update/uninstall proceeds. + * + * The full-screen page arms the pre-action throttle on success (ajax.preupdate + * Ack), so the server-side onExtensionBefore(Update|Uninstall) backup is then + * skipped — no duplicate backup. + * + * Config: Joomla.getOptions('plg_system_mokosuitebackup.installer'). + */ +(function () { + 'use strict'; + + var cfg = (window.Joomla && Joomla.getOptions) + ? Joomla.getOptions('plg_system_mokosuitebackup.installer', null) + : null; + + if (!cfg || !cfg.runbackupUrl) { + return; + } + + var STORAGE_KEY = 'msbInstallerBackup'; + + /* Toolbar tasks to intercept, gated by which feature is enabled. */ + var TASKS = {}; + if (cfg.backupBeforeUpdate) { TASKS['update.update'] = true; } + if (cfg.backupBeforeUninstall) { TASKS['manage.remove'] = true; } + + function checkedCids() { + var form = document.getElementById('adminForm'); + var cids = []; + + if (form) { + form.querySelectorAll('input[name="cid[]"]:checked').forEach(function (b) { + cids.push(b.value); + }); + } + + return cids; + } + + /* On return from the backup screen: restore the selection and re-submit. */ + function resumeIfNeeded() { + var params = new URLSearchParams(window.location.search); + + if (params.get('msb_resume') !== '1') { + return; + } + + var raw = null; + try { raw = sessionStorage.getItem(STORAGE_KEY); } catch (e) {} + try { sessionStorage.removeItem(STORAGE_KEY); } catch (e) {} + + if (!raw) { + return; + } + + var data; + try { data = JSON.parse(raw); } catch (e) { data = null; } + + if (!data || !data.task) { + return; + } + + var form = document.getElementById('adminForm'); + + if (!form) { + return; + } + + (data.cids || []).forEach(function (id) { + var box = form.querySelector('input[name="cid[]"][value="' + id + '"]'); + if (box) { box.checked = true; } + }); + + /* Re-run the original toolbar action; the wrapper lets it through + because the resume flag is set. */ + window.__msbResuming = true; + + if (window.Joomla && typeof Joomla.submitbutton === 'function') { + Joomla.submitbutton(data.task); + } else if (typeof window.submitbutton === 'function') { + window.submitbutton(data.task); + } + } + + /* Wrap Joomla.submitbutton to intercept the update/uninstall tasks. */ + function wrapSubmit() { + var J = window.Joomla; + + if (!J || typeof J.submitbutton !== 'function' || J.submitbutton.__msbWrapped) { + return; + } + + var original = J.submitbutton; + + var wrapped = function (task) { + /* Skip when resuming after a backup, when a backup already ran this + session (throttle window), or for tasks we don't handle. */ + if (window.__msbResuming || cfg.recentBackup || !TASKS[task]) { + return original.apply(J, arguments); + } + + var cids = checkedCids(); + + /* Nothing selected — let Joomla show its own "please select" notice + and don't run a pointless backup. */ + if (!cids.length) { + return original.apply(J, arguments); + } + + try { + sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ task: task, cids: cids })); + } catch (e) {} + + var ret = window.location.href; + ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1'; + + window.location.href = cfg.runbackupUrl + + '&profile_id=' + encodeURIComponent(cfg.profileId) + + '&returnurl=' + encodeURIComponent(btoa(ret)); + }; + + wrapped.__msbWrapped = true; + J.submitbutton = wrapped; + } + + /* Wrap as early as possible, and again once the DOM/core JS is ready. */ + wrapSubmit(); + + document.addEventListener('DOMContentLoaded', function () { + wrapSubmit(); + resumeIfNeeded(); + }); +})(); diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 518ae801..115b64eb 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -29,6 +29,10 @@ language/en-GB/plg_system_mokosuitebackup.sys.ini + + js + +
diff --git a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php index a7babc7c..0782c17a 100644 --- a/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php +++ b/source/packages/plg_system_mokosuitebackup/src/Extension/MokoSuiteBackup.php @@ -13,8 +13,10 @@ namespace Joomla\Plugin\System\MokoSuiteBackup\Extension; defined('_JEXEC') or die; use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Document\HtmlDocument; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\CMSPlugin; +use Joomla\CMS\Router\Route; use Joomla\CMS\Session\Session; use Joomla\CMS\Uri\Uri; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\RetentionManager; @@ -31,6 +33,7 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface return [ 'onAfterInitialise' => 'onAfterInitialise', 'onAfterRoute' => 'onAfterRoute', + 'onBeforeCompileHead' => 'onBeforeCompileHead', 'onExtensionBeforeUpdate' => 'onExtensionBeforeUpdate', 'onExtensionBeforeUninstall' => 'onExtensionBeforeUninstall', ]; @@ -145,6 +148,84 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface $this->cleanupOldSnapshots(); } + /** + * Inject the client-side interceptor on the Extensions → Update and + * Extensions → Manage pages, so an EXTENSION update / uninstall also runs + * through the full-screen backup page. + * + * Core Joomla updates are handled server-side (see maybeRedirectForUpdate), + * but com_installer's update.update / manage.remove are POST actions with a + * CSRF token and a checked cid[] list — a server-side GET redirect would drop + * the selection and fail the token check. So the JS wraps Joomla.submitbutton, + * stores the selection, sends the browser to view=runbackup, and on return + * restores the selection and re-submits the real POST form. + */ + public function onBeforeCompileHead(Event $event): void + { + $app = $this->getApplication(); + + if (!$app->isClient('administrator')) { + return; + } + + $input = $app->getInput(); + + if ($input->getCmd('option', '') !== 'com_installer') { + return; + } + + $view = $input->getCmd('view', ''); + + if ($view !== 'update' && $view !== 'manage') { + return; + } + + $params = ComponentHelper::getParams('com_mokosuitebackup'); + $beforeUpdate = (int) $params->get('backup_before_update', 0); + $beforeUninst = (int) $params->get('backup_before_uninstall', 0); + + // Relevant feature for this view must be on. + $active = ($view === 'update' && $beforeUpdate) || ($view === 'manage' && $beforeUninst); + + if (!$active) { + return; + } + + // Super Users only. + $user = $app->getIdentity(); + + if (!$user || $user->guest || !$user->authorise('core.admin')) { + return; + } + + $doc = $app->getDocument(); + + if (!$doc instanceof HtmlDocument) { + 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->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, + ]); + + $doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/installer-backup.js', [], ['defer' => true]); + } + /** * Send a Joomla core update through the full-screen backup page BEFORE the * update applies (Akeeba-style), so no backup runs synchronously inside the From 4e5bf99f61639730d9534956ced1eabaf4117e69 Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Fri, 10 Jul 2026 15:54:02 +0000 Subject: [PATCH 32/51] chore(version): pre-release bump to 02.58.20-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.20.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.20.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index f70a189d..fd83b23a 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.19 +# VERSION: 02.58.20 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 9e0aa406..d46214dc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.19 +VERSION: 02.58.20 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 3d15dc7e..546dac09 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.19 + 02.58.20 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.20.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.20.sql new file mode 100644 index 00000000..5fdbdc3b --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.20.sql @@ -0,0 +1 @@ +/* 02.58.20 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 24a0764a..a5b2f370 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.19 + 02.58.20 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 5c43ab09..47ea83a8 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.19 + 02.58.20 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index edd7e98b..3aa816f5 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.19 + 02.58.20 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index e95fb7f9..a438b8aa 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.19 + 02.58.20 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 3bca7be4..0b68be93 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.19 + 02.58.20 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 115b64eb..401a85fe 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.19 + 02.58.20 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index d6f0b141..8876032b 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.19 + 02.58.20 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index a80405c6..66391a7c 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.19 + 02.58.20 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index f6b972f0..13df5848 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.19 + 02.58.20 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 92d680d33340115116626d462128ae25526b5caa Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Fri, 10 Jul 2026 16:14:24 +0000 Subject: [PATCH 33/51] chore(version): pre-release bump to 02.58.21-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.21.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.21.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index fd83b23a..1d7fd2dd 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.20 +# VERSION: 02.58.21 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index d46214dc..487265c8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.20 +VERSION: 02.58.21 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 546dac09..2688e2ff 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.20 + 02.58.21 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.21.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.21.sql new file mode 100644 index 00000000..407b0824 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.21.sql @@ -0,0 +1 @@ +/* 02.58.21 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index a5b2f370..ff678d71 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.20 + 02.58.21 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 47ea83a8..ef7ce0b8 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.20 + 02.58.21 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 3aa816f5..1dcf78ed 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.20 + 02.58.21 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index a438b8aa..c3e9f996 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.20 + 02.58.21 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 0b68be93..285e0cae 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.20 + 02.58.21 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 401a85fe..3601e113 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.20 + 02.58.21 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 8876032b..33a00c98 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.20 + 02.58.21 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 66391a7c..68626a1b 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.20 + 02.58.21 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 13df5848..856888ba 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.20 + 02.58.21 2026-06-02 Moko Consulting hello@mokoconsulting.tech From 49fcd5e63c014afef9d4adf49c61b7dd962e8b97 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Fri, 10 Jul 2026 11:40:51 -0500 Subject: [PATCH 34/51] feat(#237): portable snapshot transfer + master->slave injection API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the first increment of snapshot transfer: - SnapshotPortable engine: package a snapshot into a portable .msbsnap (zip of manifest.json + snapshot.json + sha256), and ingest one back (from a zip upload or an inline payload) by materialising a local snapshot record. - Admin: per-row 'Download portable snapshot' + an Import modal (upload .msbsnap) on the Snapshots page. - API: POST /snapshot/inject — master pushes a snapshot payload into a slave's Web Services API; gated by a new 'Allow Snapshot Injection' option. Route added to the webservices plugin. - Restore engine: new 'create' mode (skip existing, insert only new) and mode-name normalisation (overwrite->replace, create, duplicate). The 'duplicate' (insert-as-new-with-remapped-IDs) mode returns a clear 'not yet available' — deferred to a dedicated, tested pass. - Options: 'Snapshot Transfer' fieldset — default conflict mode + allow-inject toggle. Language keys added (en-GB fallback). Reuses the existing mokosuitebackup.snapshot.manage ACL. NOTE: untested end-to-end (no dev build this session) — verify download/import/inject on a dev build. Refs #237 Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i --- CHANGELOG.md | 1 + .../src/Controller/SnapshotsController.php | 88 +++++- .../packages/com_mokosuitebackup/config.xml | 25 ++ .../language/en-GB/com_mokosuitebackup.ini | 16 + .../src/Controller/SnapshotsController.php | 90 ++++++ .../src/Engine/SnapshotPortable.php | 285 ++++++++++++++++++ .../src/Engine/SnapshotRestoreEngine.php | 82 ++++- .../tmpl/snapshots/default.php | 39 +++ .../Extension/MokoSuiteBackupWebServices.php | 11 + 9 files changed, 633 insertions(+), 4 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/src/Engine/SnapshotPortable.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fec5ae0..ac80c644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] ### 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. (The **duplicate** mode — insert as new copies with remapped IDs — is recognised but returns a clear "not yet available"; it's the next dedicated, test-worthy piece because it needs article/category/tag/field ID remapping.) - Full-screen backup now also fronts **extension updates and uninstalls** (Extensions → Update / Manage), not just core Joomla updates. Because `com_installer`'s `update.update` / `manage.remove` are POST actions (CSRF token + a checked `cid[]` list) that a server-side redirect can't cleanly resume, this is done client-side: the toolbar Update/Uninstall click is intercepted, the selection is captured, the full-screen backup runs, and on return the original selection is restored and the real POST form is re-submitted. Gated by `backup_before_update` / `backup_before_uninstall`, super-user only, and skipped if a backup already ran this session. - Manual **"Backup Now"** completion now offers a **View backup record** button (links straight to the record that was just created). It appears only for manual backups — the pre-update / pre-uninstall flow hands control back to Joomla instead. - **Full-screen backup screen** (`view=runbackup`) for both pre-update and manual backups, modelled on Akeeba's Backup-on-Update — replaces the earlier popup-modal approach. Clicking Joomla's **Install the update** now redirects (server-side, Akeeba-style) to a dedicated full-page backup screen that runs the stepped backup with a real progress bar and then **automatically continues the update** via a validated `returnurl` (flagged `is_backed_up=1` so the backup isn't repeated). Crucially, **no backup runs synchronously inside the update request** — which is what white-screened large sites. The dashboard **Backup Now** now opens the same full-screen screen instead of an inline modal. (#196) diff --git a/source/packages/com_mokosuitebackup/api/src/Controller/SnapshotsController.php b/source/packages/com_mokosuitebackup/api/src/Controller/SnapshotsController.php index 0d77d965..f3aa0f3a 100644 --- a/source/packages/com_mokosuitebackup/api/src/Controller/SnapshotsController.php +++ b/source/packages/com_mokosuitebackup/api/src/Controller/SnapshotsController.php @@ -21,9 +21,11 @@ namespace Joomla\Component\MokoSuiteBackup\Api\Controller; defined('_JEXEC') or die; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\MVC\Controller\ApiController; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotEngine; +use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotPortable; use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotRestoreEngine; class SnapshotsController extends ApiController @@ -159,11 +161,11 @@ class SnapshotsController extends ApiController $data = json_decode($this->input->json->getRaw(), true) ?: []; - $mode = $data['mode'] ?? 'replace'; + $mode = (string) ($data['mode'] ?? 'replace'); $contentTypes = $data['content_types'] ?? []; - // Enforce valid restore mode - if (!in_array($mode, ['replace', 'merge'], true)) { + // Accept the external mode names too; the engine normalises + validates. + if (!in_array($mode, ['replace', 'merge', 'overwrite', 'create', 'duplicate'], true)) { $mode = 'replace'; } @@ -183,6 +185,86 @@ class SnapshotsController extends ApiController return $this; } + /** + * Inject a snapshot payload directly into this site (master → slave). + * POST /api/index.php/v1/mokosuitebackup/snapshot/inject + * + * Body: { + * "snapshot": { version, content_types, tables }, // the payload (required) + * "mode": "overwrite" | "create" | "duplicate", // optional; defaults to the site's snapshot_inject_mode + * "content_types": [ ... ], // optional filter + * "description": "..." // optional + * } + */ + public function inject(): static + { + if (!$this->app->getIdentity()->authorise('mokosuitebackup.snapshot.manage', 'com_mokosuitebackup')) { + $this->app->setHeader('status', 403); + echo json_encode(['errors' => [['title' => 'Access denied']]]); + $this->app->close(); + + return $this; + } + + $params = ComponentHelper::getParams('com_mokosuitebackup'); + + // The receiving (slave) site must explicitly allow injection. + if (!(int) $params->get('snapshot_allow_inject', 0)) { + $this->app->setHeader('status', 403); + echo json_encode(['errors' => [['title' => 'Snapshot injection is disabled on this site']]]); + $this->app->close(); + + return $this; + } + + $body = json_decode($this->input->json->getRaw(), true) ?: []; + $snapshot = $body['snapshot'] ?? null; + + if (!is_array($snapshot)) { + $this->app->setHeader('status', 400); + echo json_encode(['errors' => [['title' => 'Missing "snapshot" payload']]]); + $this->app->close(); + + return $this; + } + + // Explicit mode wins; otherwise the site default from Options. + $mode = (string) ($body['mode'] ?? $params->get('snapshot_inject_mode', 'overwrite')); + $contentTypes = $body['content_types'] ?? []; + $description = (string) ($body['description'] ?? 'Injected snapshot'); + + // Materialise a local record from the payload, then restore it. + $ingest = SnapshotPortable::ingestData($snapshot, $description); + + if (empty($ingest['success'])) { + $this->app->setHeader('status', 422); + echo json_encode(['errors' => [['title' => $ingest['message']]]]); + $this->app->close(); + + return $this; + } + + $engine = new SnapshotRestoreEngine(); + $result = $engine->restore((int) $ingest['id'], $mode, $contentTypes); + $result['snapshot_id'] = (int) $ingest['id']; + + if (!empty($ingest['warnings'])) { + $result['warnings'] = array_merge($result['warnings'] ?? [], $ingest['warnings']); + } + + if ($result['success']) { + $this->app->setHeader('status', 200); + echo json_encode(['data' => $result]); + } else { + $this->app->setHeader('status', 500); + echo json_encode(['errors' => [['title' => $result['message']]], 'data' => $result]); + } + + $this->app->close(); + + return $this; + } + /** * Delete a snapshot record and its data file (DELETE /api/index.php/v1/mokosuitebackup/snapshot/:id) */ diff --git a/source/packages/com_mokosuitebackup/config.xml b/source/packages/com_mokosuitebackup/config.xml index 84db7fca..f0ce6bda 100644 --- a/source/packages/com_mokosuitebackup/config.xml +++ b/source/packages/com_mokosuitebackup/config.xml @@ -206,6 +206,31 @@ />
+
+ + + + + + + + + +
+
checkToken('get'); + + if (!$this->app->getIdentity()->authorise('mokosuitebackup.snapshot.manage', 'com_mokosuitebackup')) { + $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error'); + $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false)); + + return; + } + + $id = $this->input->getInt('id', 0); + $item = $id ? $this->getModel('Snapshot')->getItem($id) : null; + + if (!$item || empty($item->id)) { + $this->setMessage(Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_NOT_FOUND'), 'error'); + $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false)); + + return; + } + + try { + $file = SnapshotPortable::package($item); + } catch (\Throwable $e) { + $this->setMessage($e->getMessage(), 'error'); + $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false)); + + return; + } + + $name = SnapshotPortable::filename($item); + + while (@ob_end_clean()) { + // flush buffers + } + + header('Content-Type: application/octet-stream'); + header("Content-Disposition: attachment; filename*=UTF-8''" . rawurlencode($name)); + header('Content-Length: ' . filesize($file)); + header('Cache-Control: no-cache, must-revalidate'); + + readfile($file); + @unlink($file); + + $this->app->close(); + } + + /** + * Import an uploaded .msbsnap file — materialise a local snapshot record. + */ + public function import(): void + { + $this->checkToken(); + + if (!$this->app->getIdentity()->authorise('mokosuitebackup.snapshot.manage', 'com_mokosuitebackup')) { + $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error'); + $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false)); + + return; + } + + $file = $this->input->files->get('msbsnap_file', null, 'raw'); + + if (!is_array($file) || empty($file['tmp_name']) || (int) ($file['error'] ?? 1) !== 0 + || !is_uploaded_file($file['tmp_name'])) { + $this->setMessage(Text::_('COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT_NO_FILE'), 'error'); + $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false)); + + return; + } + + $result = SnapshotPortable::ingestZip($file['tmp_name']); + + if (!empty($result['success'])) { + foreach ($result['warnings'] ?? [] as $warning) { + $this->app->enqueueMessage($warning, 'warning'); + } + + $this->setMessage($result['message']); + } else { + $this->setMessage($result['message'], 'error'); + } + + $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=snapshots', false)); + } + /** * Create a new content snapshot. */ diff --git a/source/packages/com_mokosuitebackup/src/Engine/SnapshotPortable.php b/source/packages/com_mokosuitebackup/src/Engine/SnapshotPortable.php new file mode 100644 index 00000000..97627d22 --- /dev/null +++ b/source/packages/com_mokosuitebackup/src/Engine/SnapshotPortable.php @@ -0,0 +1,285 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + * + * Portable snapshot transfer format (.msbsnap). + * + * A .msbsnap is a zip containing: + * - manifest.json — format/version, generator, source (host/Joomla/prefix), + * content summary, and a sha256 of the payload. + * - snapshot.json — the snapshot's content data (verbatim, same shape the + * snapshot engine writes: {version, content_types, tables}). + * + * package() builds a .msbsnap for an existing snapshot record (for download). + * ingestZip() validates an uploaded .msbsnap and materialises a local snapshot + * record from it (so the normal restore UX can act on it). + * ingestData() materialises a record from an already-parsed data array (used by + * the master→slave injection API, which receives the payload inline). + */ + +namespace Joomla\Component\MokoSuiteBackup\Administrator\Engine; + +defined('_JEXEC') or die; + +use Joomla\CMS\Factory; +use Joomla\CMS\Uri\Uri; +use Joomla\Component\MokoSuiteBackup\Administrator\Utility\BackupDirectory; + +final class SnapshotPortable +{ + public const FORMAT = 'msbsnap'; + public const FORMAT_VERSION = 1; + public const MANIFEST_NAME = 'manifest.json'; + public const PAYLOAD_NAME = 'snapshot.json'; + + /** + * Build a portable .msbsnap zip for a snapshot record. + * + * @param object $snapshot Row from #__mokosuitebackup_snapshots (needs + * data_file, description, content_types, *_count) + * + * @return string Absolute path to a temp .msbsnap file (caller unlinks it) + * + * @throws \RuntimeException + */ + public static function package(object $snapshot): string + { + $dataFile = (string) ($snapshot->data_file ?? ''); + + if ($dataFile === '' || !is_file($dataFile) || !is_readable($dataFile)) { + throw new \RuntimeException('Snapshot data file is missing or unreadable.'); + } + + $payload = file_get_contents($dataFile); + + if ($payload === false) { + throw new \RuntimeException('Could not read snapshot data file.'); + } + + $manifest = [ + 'format' => self::FORMAT, + 'format_version' => self::FORMAT_VERSION, + 'generator' => 'MokoSuiteBackup', + 'created' => Factory::getDate()->toISO8601(), + 'source' => [ + 'host' => self::siteHost(), + 'joomla_version' => JVERSION, + 'db_prefix' => Factory::getDbo()->getPrefix(), + ], + 'snapshot' => [ + 'description' => (string) ($snapshot->description ?? ''), + 'content_types' => json_decode((string) ($snapshot->content_types ?? '[]'), true) ?: [], + 'articles_count' => (int) ($snapshot->articles_count ?? 0), + 'categories_count' => (int) ($snapshot->categories_count ?? 0), + 'modules_count' => (int) ($snapshot->modules_count ?? 0), + 'created' => (string) ($snapshot->created ?? ''), + ], + 'payload' => [ + 'file' => self::PAYLOAD_NAME, + 'sha256' => hash('sha256', $payload), + 'size' => strlen($payload), + ], + ]; + + $tmp = tempnam(sys_get_temp_dir(), 'msbsnap_'); + + if ($tmp === false) { + throw new \RuntimeException('Could not create a temporary file.'); + } + + $zip = new \ZipArchive(); + + if ($zip->open($tmp, \ZipArchive::OVERWRITE) !== true) { + @unlink($tmp); + + throw new \RuntimeException('Could not create the .msbsnap archive.'); + } + + $zip->addFromString(self::MANIFEST_NAME, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + $zip->addFromString(self::PAYLOAD_NAME, $payload); + $zip->close(); + + return $tmp; + } + + /** + * A friendly download filename for a snapshot's portable file. + */ + public static function filename(object $snapshot): string + { + $id = (int) ($snapshot->id ?? 0); + $date = date('Ymd_His'); + + return 'snapshot-' . $id . '-' . $date . '.msbsnap'; + } + + /** + * Ingest an uploaded .msbsnap file: validate it and materialise a local + * snapshot record so the normal restore UX can act on it. + * + * @return array{success:bool,message:string,id?:int,warnings?:array} + */ + public static function ingestZip(string $zipPath, string $descriptionPrefix = 'Imported'): array + { + if (!is_file($zipPath) || !is_readable($zipPath)) { + return ['success' => false, 'message' => 'Uploaded file is missing or unreadable.']; + } + + $zip = new \ZipArchive(); + + if ($zip->open($zipPath) !== true) { + return ['success' => false, 'message' => 'Not a valid .msbsnap archive.']; + } + + try { + $manifestRaw = $zip->getFromName(self::MANIFEST_NAME); + $payloadRaw = $zip->getFromName(self::PAYLOAD_NAME); + } finally { + $zip->close(); + } + + if ($manifestRaw === false || $payloadRaw === false) { + return ['success' => false, 'message' => 'Archive is missing its manifest or payload.']; + } + + $manifest = json_decode($manifestRaw, true); + + if (!is_array($manifest) || ($manifest['format'] ?? '') !== self::FORMAT) { + return ['success' => false, 'message' => 'Unrecognised snapshot file format.']; + } + + if ((int) ($manifest['format_version'] ?? 0) > self::FORMAT_VERSION) { + return ['success' => false, 'message' => 'This snapshot file was made by a newer version of MokoSuiteBackup.']; + } + + $expected = (string) ($manifest['payload']['sha256'] ?? ''); + + if ($expected !== '' && !hash_equals($expected, hash('sha256', $payloadRaw))) { + return ['success' => false, 'message' => 'Snapshot payload failed its integrity (checksum) check.']; + } + + $data = json_decode($payloadRaw, true); + + if (!self::isValidData($data)) { + return ['success' => false, 'message' => 'Snapshot payload is not valid content data.']; + } + + $warnings = self::compatWarnings($manifest); + $srcHost = (string) ($manifest['source']['host'] ?? ''); + $srcDesc = (string) ($manifest['snapshot']['description'] ?? ''); + $descr = trim($descriptionPrefix . ': ' . ($srcDesc !== '' ? $srcDesc : 'snapshot') + . ($srcHost !== '' ? ' (from ' . $srcHost . ')' : '')); + + $result = self::ingestData($data, $descr); + + if (!empty($warnings) && !empty($result['success'])) { + $result['warnings'] = array_merge($result['warnings'] ?? [], $warnings); + } + + return $result; + } + + /** + * Materialise a snapshot record from an already-parsed data array. + * Used by the injection API (payload arrives inline, not as a zip). + * + * @return array{success:bool,message:string,id?:int,warnings?:array} + */ + public static function ingestData(array $data, string $description): array + { + if (!self::isValidData($data)) { + return ['success' => false, 'message' => 'Snapshot data is invalid or empty.']; + } + + try { + $backupDir = BackupDirectory::getDefaultAbsolute(); + BackupDirectory::ensureReady($backupDir); + + $filename = 'snapshot_' . date('Ymd_His') . '_imported.json'; + $filePath = $backupDir . '/' . $filename; + + $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + if ($json === false || file_put_contents($filePath, $json) === false) { + return ['success' => false, 'message' => 'Could not write the imported snapshot file.']; + } + + $tables = $data['tables'] ?? []; + $db = Factory::getDbo(); + + $record = (object) [ + 'description' => $description !== '' ? $description : 'Imported snapshot', + 'content_types' => json_encode(array_values($data['content_types'] ?? [])), + 'status' => 'complete', + 'articles_count' => count($tables['#__content'] ?? []), + 'categories_count' => count($tables['#__categories'] ?? []), + 'modules_count' => count($tables['#__modules'] ?? []), + 'data_file' => $filePath, + 'data_size' => strlen($json), + 'log' => 'Imported ' . Factory::getDate()->toSql(), + 'created' => Factory::getDate()->toSql(), + 'created_by' => (int) (Factory::getApplication()->getIdentity()->id ?? 0), + ]; + + $db->insertObject('#__mokosuitebackup_snapshots', $record, 'id'); + + return [ + 'success' => true, + 'message' => 'Snapshot imported (record #' . (int) $record->id . ').', + 'id' => (int) $record->id, + 'warnings' => [], + ]; + } catch (\Throwable $e) { + return ['success' => false, 'message' => 'Import failed: ' . $e->getMessage()]; + } + } + + /** + * Structural check for a snapshot data payload. + */ + private static function isValidData($data): bool + { + return is_array($data) + && isset($data['tables']) && is_array($data['tables']) + && isset($data['content_types']) && is_array($data['content_types']); + } + + /** + * Best-effort compatibility warnings (non-blocking). + * + * @return string[] + */ + private static function compatWarnings(array $manifest): array + { + $warnings = []; + $srcJoomla = (string) ($manifest['source']['joomla_version'] ?? ''); + $srcMajor = $srcJoomla !== '' ? (int) explode('.', $srcJoomla)[0] : 0; + $thisMajor = (int) explode('.', JVERSION)[0]; + + if ($srcMajor > 0 && $srcMajor !== $thisMajor) { + $warnings[] = 'Snapshot was created on Joomla ' . $srcJoomla . ' but this site is Joomla ' . JVERSION + . ' — content tables may differ; review after restoring.'; + } + + return $warnings; + } + + /** + * The current site's host, for the manifest (best-effort, informational). + */ + private static function siteHost(): string + { + try { + $host = Uri::getInstance(Uri::root())->getHost(); + } catch (\Throwable $e) { + $host = ''; + } + + return $host !== '' ? $host : (string) (Factory::getApplication()->get('sitename', 'site')); + } +} diff --git a/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php b/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php index 0bf3c300..40fdd32a 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php @@ -59,7 +59,19 @@ class SnapshotRestoreEngine $this->log('WARNING: Could not increase memory limit to 512M'); } - if (!in_array($mode, ['replace', 'merge'])) { + $mode = $this->normaliseMode($mode); + + if ($mode === 'duplicate') { + // Insert-as-new-with-remapped-IDs is a separate, heavier engine + // (article + category nested-set + tag/field/workflow reference + // remapping) that needs its own tested implementation. + return [ + 'success' => false, + 'message' => 'Duplicate mode (insert as new with remapped IDs) is not available yet — use overwrite or create.', + ]; + } + + if (!in_array($mode, ['replace', 'merge', 'create'], true)) { return ['success' => false, 'message' => 'Invalid restore mode: ' . $mode]; } @@ -140,6 +152,8 @@ class SnapshotRestoreEngine if ($mode === 'replace') { $rowCount = $this->restoreReplace($db, $realTable, $abstractTable, $rows); + } elseif ($mode === 'create') { + $rowCount = $this->restoreCreate($db, $realTable, $abstractTable, $rows); } else { $rowCount = $this->restoreMerge($db, $realTable, $abstractTable, $rows); } @@ -264,6 +278,72 @@ class SnapshotRestoreEngine return $count; } + /** + * Create mode: insert only rows that don't already exist; never overwrite. + * Non-destructive — existing content on the target is left untouched. Used + * by master→slave injection when the master must not clobber local edits. + */ + private function restoreCreate(object $db, string $realTable, string $abstractTable, array $rows): int + { + $pk = self::PRIMARY_KEYS[$abstractTable] ?? null; + $count = 0; + + foreach ($rows as $row) { + $obj = (object) $row; + + if ($pk !== null && isset($row[$pk])) { + $exists = $db->setQuery( + $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName($realTable)) + ->where($db->quoteName($pk) . ' = ' . $db->quote($row[$pk])) + )->loadResult(); + + if ($exists) { + continue; + } + + $db->insertObject($realTable, $obj); + } else { + // Composite-key tables: insert, skip genuine duplicates. + try { + $db->insertObject($realTable, $obj); + } catch (\Exception $e) { + if (str_contains($e->getMessage(), 'Duplicate entry') || $e->getCode() === 1062) { + continue; + } + + throw $e; + } + } + + $count++; + } + + return $count; + } + + /** + * Normalise external mode names to the engine's internal modes. + * overwrite→replace, skip/new→create, copy→duplicate; passthrough otherwise. + */ + private function normaliseMode(string $mode): string + { + $map = [ + 'overwrite' => 'replace', + 'replace' => 'replace', + 'update' => 'merge', + 'merge' => 'merge', + 'skip' => 'create', + 'new' => 'create', + 'create' => 'create', + 'copy' => 'duplicate', + 'duplicate' => 'duplicate', + ]; + + return $map[strtolower(trim($mode))] ?? strtolower(trim($mode)); + } + /** * Delete rows from a table, scoping to relevant content only. * diff --git a/source/packages/com_mokosuitebackup/tmpl/snapshots/default.php b/source/packages/com_mokosuitebackup/tmpl/snapshots/default.php index 53fce3e0..f2508b97 100644 --- a/source/packages/com_mokosuitebackup/tmpl/snapshots/default.php +++ b/source/packages/com_mokosuitebackup/tmpl/snapshots/default.php @@ -27,6 +27,14 @@ $listDirn = $this->escape($this->state->get('list.direction'));
+ +
+ +
+ items)) : ?>
@@ -112,6 +120,11 @@ $listDirn = $this->escape($this->state->get('list.direction')); title=""> + + + id; ?> @@ -131,6 +144,32 @@ $listDirn = $this->escape($this->state->get('list.direction'));
+ + + + + +
+
0%
+
@@ -126,6 +174,7 @@ $config = [ var el = { bar: document.getElementById('msb-rb-bar'), + track: document.querySelector('.msb-rb-progress'), phase: document.getElementById('msb-rb-phase'), status: document.getElementById('msb-rb-status'), title: document.getElementById('msb-rb-title'), @@ -140,9 +189,9 @@ $config = [ pct = Math.max(0, Math.min(100, parseInt(pct, 10) || 0)); el.bar.style.width = pct + '%'; el.bar.textContent = pct + '%'; - el.bar.setAttribute('aria-valuenow', pct); + if (el.track) { el.track.setAttribute('aria-valuenow', pct); } if (!striped) { - el.bar.classList.remove('progress-bar-striped', 'progress-bar-animated'); + el.bar.classList.remove('is-animated'); } } function setPhase(t) { el.phase.textContent = t || ''; } @@ -167,8 +216,8 @@ $config = [ } async function onComplete() { - el.bar.classList.remove('progress-bar-striped', 'progress-bar-animated'); - el.bar.classList.add('bg-success'); + el.bar.classList.remove('is-animated'); + el.bar.classList.add('is-success'); setBar(100, false); el.title.textContent = L.complete || 'Backup complete'; @@ -198,8 +247,8 @@ $config = [ function onError(message) { running = false; - el.bar.classList.remove('progress-bar-striped', 'progress-bar-animated'); - el.bar.classList.add('bg-danger'); + el.bar.classList.remove('is-animated'); + el.bar.classList.add('is-danger'); el.title.textContent = L.failed || 'Backup failed'; setStatus(message || 'Backup failed'); showActions(); @@ -209,8 +258,8 @@ $config = [ el.retry.onclick = function () { el.retry.classList.add('d-none'); el.continue.classList.add('d-none'); - el.bar.classList.remove('bg-danger'); - el.bar.classList.add('progress-bar-striped', 'progress-bar-animated'); + el.bar.classList.remove('is-danger'); + el.bar.classList.add('is-animated'); run(); }; diff --git a/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js b/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js index 1153e59a..6f80f850 100644 --- a/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js +++ b/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js @@ -1,17 +1,17 @@ /** * MokoSuiteBackup — full-screen backup before an EXTENSION update / uninstall. * - * Injected by plg_system_mokosuitebackup on the Extensions → Update and - * Extensions → Manage pages. Unlike the core Joomla update (which has a - * server-side page load we can redirect), com_installer's update.update and - * manage.remove are POST actions with a CSRF token and a checked cid[] list — - * so we intercept CLIENT-SIDE: capture the selection, go to the full-screen - * backup page, and on return restore the selection and re-submit the real POST - * form (fresh token) so the update/uninstall proceeds. + * Injected by plg_system_mokosuitebackup on Extensions → Update and + * Extensions → Manage. Joomla 6 renders those toolbar buttons as web + * components (`