From 07fb4dcc248cf6deb9e0c1592c09e0dcdf88b14f Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 25 Jun 2026 10:50:31 -0500 Subject: [PATCH] fix: remove run/backup buttons, move actions to detail view, custom restore script name, version bump 01.43.11-dev - Remove Run Backup / Backup Now buttons from profiles list, profile edit toolbar, and backup records view - Move download, browse archive, and view log from backup list rows into individual backup record detail view - Add download button to backup detail toolbar - Link profile column in backup records list to profile edit - Complete restore script filename customization across BackupEngine, SteppedBackupEngine, and MokoRestore - Remove ordering field from profiles, default sort by ID ascending - Fix untranslated JFIELD language keys - Bump all manifests to 01.43.11-dev --- CHANGELOG.md | 24 +- .../forms/filter_profiles.xml | 3 +- .../com_mokosuitebackup/forms/profile.xml | 16 +- .../language/en-GB/com_mokosuitebackup.ini | 2 + .../com_mokosuitebackup/mokosuitebackup.xml | 3 +- .../src/Engine/BackupEngine.php | 25 +- .../src/Engine/MokoRestore.php | 36 ++- .../src/Engine/SteppedBackupEngine.php | 28 ++- .../src/Engine/SteppedSession.php | 4 +- .../src/Model/ProfilesModel.php | 4 +- .../src/View/Backup/HtmlView.php | 22 ++ .../src/View/Backups/HtmlView.php | 15 -- .../src/View/Profile/HtmlView.php | 10 - .../tmpl/backups/default.php | 238 +----------------- .../tmpl/profiles/default.php | 13 - .../mod_mokosuitebackup_cpanel.xml | 3 +- .../mokosuitebackup.xml | 3 +- .../mokosuitebackup.xml | 3 +- .../mokosuitebackup.xml | 3 +- .../mokosuitebackup.xml | 3 +- .../mokosuitebackup.xml | 3 +- .../mokosuitebackup.xml | 3 +- .../mokosuitebackup.xml | 3 +- source/pkg_mokosuitebackup.xml | 3 +- 24 files changed, 146 insertions(+), 324 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e02f3..2d8435f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ ## [Unreleased] +### Added +- Customizable restore script filename per backup profile (reduces discoverability on remote servers) +- MokoRestore standalone mode: multi-ZIP selector when multiple backup archives are present +- MokoRestore preflight: Joomla installation detection warning before overwriting an existing site +- MokoRestore error handling: try/catch on fetch calls, HTTP status checks, JSON parse recovery +- Download button on individual backup record detail toolbar +- Profile column in backup records list links to the profile edit view + +### Changed +- Moved download, browse archive, and view log actions from backup list rows into the individual backup record view +- Removed "Run Backup" / "Backup Now" buttons from profiles list, profile edit toolbar, and backup records view (backups are triggered from the dashboard only) +- Removed ordering field from profiles; default sort is now by ID ascending +- MokoRestore cleanup and security messages now reference the actual script filename instead of hardcoded "restore.php" + +### Fixed +- Bootstrap 5 modal conversion for snapshots view (data-bs-dismiss, modal-footer, getOrCreateInstance) +- ntfy default URL changed from ntfy.sh to ntfy.mokoconsulting.tech +- Untranslated JFIELD_ORDERING_ASC / JFIELD_ORDERING_LABEL language keys replaced with component-specific keys +- Options page title now shows "MokoSuiteBackup Options" instead of raw language key +- Profile dropdown IDs in backup records and dashboard show "#ID — Title (type)" format +- MokoRestore stalling: unhandled promise rejections from network errors or non-JSON responses left UI in loading state + ## [01.43.00] --- 2026-06-24 @@ -71,7 +93,7 @@ - Backup comparison: select two backups for side-by-side diff - Archive browser: view files inside backup without extracting - Manual purge: delete backups older than a date with count preview -- Run Backup button on profile list and edit views with backup count badges +- Backup count badges on profile list - "Do not navigate away" warning in backup/restore progress modals - Clickable placeholder pills for backup directory and archive name fields - Comprehensive help modal with absolute/relative/placeholder path documentation diff --git a/source/packages/com_mokosuitebackup/forms/filter_profiles.xml b/source/packages/com_mokosuitebackup/forms/filter_profiles.xml index ff2680f..52a51dc 100644 --- a/source/packages/com_mokosuitebackup/forms/filter_profiles.xml +++ b/source/packages/com_mokosuitebackup/forms/filter_profiles.xml @@ -24,10 +24,9 @@ name="fullordering" type="list" label="JGLOBAL_SORT_BY" - default="a.ordering ASC" + default="a.id ASC" onchange="this.form.submit();" > - diff --git a/source/packages/com_mokosuitebackup/forms/profile.xml b/source/packages/com_mokosuitebackup/forms/profile.xml index 661673d..81a46de 100644 --- a/source/packages/com_mokosuitebackup/forms/profile.xml +++ b/source/packages/com_mokosuitebackup/forms/profile.xml @@ -93,6 +93,16 @@ + JPUBLISHED -
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 7d96705..8589854 100644 --- a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini +++ b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini @@ -140,6 +140,8 @@ COM_MOKOJOOMBACKUP_FIELD_INCLUDE_MOKORESTORE_DESC="None: no restore script. Wrap COM_MOKOJOOMBACKUP_MOKORESTORE_NONE="None" COM_MOKOJOOMBACKUP_MOKORESTORE_WRAPPED="Wrapped (inside backup ZIP)" COM_MOKOJOOMBACKUP_MOKORESTORE_STANDALONE="Standalone (separate restore.php)" +COM_MOKOJOOMBACKUP_FIELD_RESTORE_SCRIPT_NAME="Restore Script Filename" +COM_MOKOJOOMBACKUP_FIELD_RESTORE_SCRIPT_NAME_DESC="Custom filename for the restore script. Must end in .php. Use a non-obvious name to reduce discoverability on remote servers (e.g. moko-install-xyz.php)." ; Data Sanitization COM_MOKOJOOMBACKUP_FIELDSET_SANITIZATION="Data Sanitization" diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 88e8570..0938167 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,8 @@ --> MokoSuiteBackup - 01.43.10 + 01.43.11-dev + 01.43.11-dev 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php b/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php index 52f0a58..5eb994c 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php @@ -259,14 +259,14 @@ class BackupEngine // Step 2.5: MokoRestore script (if enabled) $mokoRestoreMode = $profile->include_mokorestore ?? '0'; + $restoreScriptName = $profile->restore_script_name ?? 'restore.php'; $restoreScriptPath = ''; if ($mokoRestoreMode === '1') { - // Wrapped mode: backup ZIP inside an outer ZIP with restore.php $this->log('Wrapping with MokoRestore script...'); $mokoRestoreName = str_replace('.zip', '-mokorestore.zip', $archiveName); $mokoRestorePath = $this->backupDir . '/' . $mokoRestoreName; - MokoRestore::wrap($archivePath, $mokoRestorePath); + MokoRestore::wrap($archivePath, $mokoRestorePath, $restoreScriptName); if (is_file($archivePath) && !unlink($archivePath)) { $this->log('WARNING: Could not remove pre-wrap archive'); @@ -278,11 +278,11 @@ class BackupEngine $this->log('MokoRestore archive created: ' . $sizeHuman); $this->log('SHA-256 (wrapped): ' . $checksum); } elseif ($mokoRestoreMode === 'standalone') { - // Standalone mode: restore.php as a separate file next to the backup ZIP - $this->log('Generating standalone restore.php...'); - $restoreScriptPath = $this->backupDir . '/restore.php'; + $restoreScriptName = MokoRestore::sanitizeScriptName($restoreScriptName); + $this->log('Generating standalone ' . $restoreScriptName . '...'); + $restoreScriptPath = $this->backupDir . '/' . $restoreScriptName; MokoRestore::generateStandalone($restoreScriptPath); - $this->log('Standalone restore.php generated (' . number_format(filesize($restoreScriptPath)) . ' bytes)'); + $this->log('Standalone ' . $restoreScriptName . ' generated (' . number_format(filesize($restoreScriptPath)) . ' bytes)'); } $remoteFilename = ''; @@ -303,9 +303,8 @@ class BackupEngine $remoteFilename = $result['remote_path'] ?? $archiveName; $this->log(' Upload complete: ' . $result['message']); - /* Upload standalone restore.php if in standalone mode */ if (!empty($restoreScriptPath) && is_file($restoreScriptPath)) { - $uploader->upload($restoreScriptPath, 'restore.php'); + $uploader->upload($restoreScriptPath, basename($restoreScriptPath)); } } else { $uploadFailed = true; @@ -336,15 +335,15 @@ class BackupEngine $remoteFilename = $uploadResult['remote_path'] ?? $archiveName; $this->log('Remote upload complete: ' . $uploadResult['message']); - // Upload standalone restore.php alongside the backup if in standalone mode if (!empty($restoreScriptPath) && is_file($restoreScriptPath)) { - $this->log('Uploading standalone restore.php...'); - $restoreUpload = $uploader->upload($restoreScriptPath, 'restore.php'); + $restoreBasename = basename($restoreScriptPath); + $this->log('Uploading standalone ' . $restoreBasename . '...'); + $restoreUpload = $uploader->upload($restoreScriptPath, $restoreBasename); if ($restoreUpload['success']) { - $this->log('Standalone restore.php uploaded'); + $this->log('Standalone ' . $restoreBasename . ' uploaded'); } else { - $this->log('WARNING: restore.php upload failed: ' . $restoreUpload['message']); + $this->log('WARNING: ' . $restoreBasename . ' upload failed: ' . $restoreUpload['message']); } } diff --git a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php index 9bc481a..78c4b2a 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php +++ b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php @@ -35,25 +35,36 @@ class MokoRestore * * @return string Path to the wrapped archive */ - public static function wrap(string $backupArchive, string $outputPath): string + public static function wrap(string $backupArchive, string $outputPath, string $scriptName = 'restore.php'): string { + $scriptName = self::sanitizeScriptName($scriptName); + $zip = new \ZipArchive(); if ($zip->open($outputPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { throw new \RuntimeException('Cannot create MokoRestore archive: ' . $outputPath); } - // Add the standalone restore script - $zip->addFromString('restore.php', self::generateRestoreScript()); - - // Add the original backup as a nested ZIP + $zip->addFromString($scriptName, self::generateRestoreScript()); $zip->addFile($backupArchive, 'site-backup.zip'); - $zip->close(); return $outputPath; } + public static function sanitizeScriptName(string $name): string + { + $name = basename(trim($name)); + + if ($name === '' || !str_ends_with(strtolower($name), '.php')) { + $name = 'restore.php'; + } + + $name = preg_replace('/[^a-zA-Z0-9._-]/', '', $name); + + return $name ?: 'restore.php'; + } + /** * Generate the standalone restore.php script as a separate file. * @@ -173,7 +184,7 @@ SCANNER; 'label' => 'Backup Archive', 'value' => file_exists(BACKUP_FILE) ? number_format(filesize(BACKUP_FILE) / 1048576, 2) . ' MB' : 'Not found', 'ok' => file_exists(BACKUP_FILE), - 'hint' => 'site-backup.zip must be in the same directory as restore.php', + 'hint' => 'site-backup.zip must be in the same directory as ' . basename($_SERVER['SCRIPT_NAME']), ]; ORIG, <<<'REPL' @@ -191,7 +202,7 @@ ORIG, 'label' => 'Backup Archive', 'value' => $archiveValue, 'ok' => $backupCount > 0, - 'hint' => 'Place one or more backup ZIP files in the same directory as restore.php', + 'hint' => 'Place one or more backup ZIP files in the same directory as ' . basename($_SERVER['SCRIPT_NAME']), ]; REPL ); @@ -484,7 +495,7 @@ function actionPreflight(): array 'label' => 'Backup Archive', 'value' => file_exists(BACKUP_FILE) ? number_format(filesize(BACKUP_FILE) / 1048576, 2) . ' MB' : 'Not found', 'ok' => file_exists(BACKUP_FILE), - 'hint' => 'site-backup.zip must be in the same directory as restore.php', + 'hint' => 'site-backup.zip must be in the same directory as ' . basename($_SERVER['SCRIPT_NAME']), ]; $checks[] = [ @@ -1540,7 +1551,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica N
- Security: Delete restore.php immediately after installation is complete. + Security: Delete immediately after installation is complete.
@@ -1788,7 +1799,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica N Success! The site restoration is complete.
- Important: Delete restore.php and site-backup.zip from your server immediately for security. + Important: Delete and site-backup.zip from your server immediately for security.
@@ -1812,6 +1823,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica N @@ -654,52 +468,6 @@ $listDirn = $this->escape($this->state->get('list.direction'));
- -