Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b175b2b3a | |||
| c48fd3b8eb | |||
| 4b17842496 | |||
| 85c0967e89 | |||
| d89812cbfb | |||
| 79efde483e | |||
| c567890bfa | |||
| ba070f004d | |||
| ce15cecf85 | |||
| 2b4af3ce28 | |||
| 1c41cdc03b | |||
| 79b23215eb | |||
| bd2effd89c | |||
| 6da7ef0b69 | |||
| cbc8627fb7 | |||
| f1dfc18f3d | |||
| 20fcd1c39c | |||
| 3fb82bc03c | |||
| 499436a8d0 | |||
| a004b9c0d9 | |||
| 5b1cf9ea8e | |||
| 86fe470158 | |||
| a6e196ffd0 | |||
| d43b9405c0 | |||
| 8506573898 | |||
| 6ad5046f82 | |||
| 20a760097b | |||
| b378794a05 | |||
| dc23f4dd5e | |||
| 9a5f4dbbb2 | |||
| 45066237b0 | |||
| 2296c2f754 | |||
| 7416c30925 | |||
| 186a75681a | |||
| 0a60436f3d | |||
| a2362d5400 | |||
| 6f1d270818 | |||
| 7aa1f2eb43 | |||
| 4339c223a1 | |||
| cb56430e37 | |||
| 67611604e2 | |||
| 00c1add0cd | |||
| 1e3f963beb | |||
| c280229b90 | |||
| fbf0a2de76 | |||
| 30b8e66eca | |||
| 83b261db02 | |||
| e0285f112f | |||
| f5538ade88 | |||
| 2d27385ac9 | |||
| a3eb713d81 | |||
| 7106ece393 | |||
| 394ad0d607 | |||
| f5bcc7c457 |
@@ -7,7 +7,7 @@ Full-site backup and restore for Joomla — database, files, and configuration.
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Package** | `pkg_mokosuitebackup` |
|
||||
| **Language** | PHP 8.1+ |
|
||||
| **Language** | PHP 8.3+ |
|
||||
| **Branch** | develop on `dev`, merge to `main` (protected) |
|
||||
| **Wiki** | [MokoSuiteBackup Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup/wiki) |
|
||||
|
||||
@@ -29,7 +29,7 @@ Joomla **package** with four sub-extensions:
|
||||
### com_mokosuitebackup (Component)
|
||||
- Admin backend for managing backup profiles and records
|
||||
- Backup engine: `Engine/BackupEngine`, `Engine/DatabaseDumper`, `Engine/FileScanner`, `Engine/Archiver`
|
||||
- Joomla 4/5 MVC: Controllers, Models, Views, Tables
|
||||
- Joomla 6 MVC: Controllers, Models, Views, Tables
|
||||
- Namespace: `Joomla\Component\MokoSuiteBackup\Administrator`
|
||||
- DB tables: `#__mokosuitebackup_profiles`, `#__mokosuitebackup_records`
|
||||
- CLI: `cli/mokosuitebackup.php` for cron-based backups
|
||||
@@ -64,8 +64,8 @@ Joomla **package** with four sub-extensions:
|
||||
|
||||
## Coding Standards
|
||||
|
||||
- PHP 8.1+ minimum
|
||||
- Joomla 4/5 DI container pattern: `services/provider.php` → Extension class
|
||||
- PHP 8.3+ minimum (Joomla 6)
|
||||
- Joomla 6 DI container pattern: `services/provider.php` → Extension class
|
||||
- Legacy stub `.php` file required for plugin loader but empty
|
||||
- `SubscriberInterface` for event subscription (not `on*` method naming)
|
||||
- `bind() → check() → store()` for Table operations (not `save()`)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: MokoGitea.Workflow
|
||||
# INGROUP: mokocli.Automation
|
||||
# VERSION: 02.60.00
|
||||
# VERSION: 02.63.08
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
@@ -46,13 +46,13 @@ jobs:
|
||||
WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||
URL="${{ github.event.workflow_run.html_url }}"
|
||||
|
||||
curl -sS \
|
||||
curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
|
||||
-H "Title: ${REPO} released" \
|
||||
-H "Tags: white_check_mark,package" \
|
||||
-H "Priority: default" \
|
||||
-H "Click: ${URL}" \
|
||||
-d "${WORKFLOW} completed successfully." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}"
|
||||
"${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
|
||||
|
||||
- name: Notify on failure
|
||||
if: github.event.workflow_run.conclusion == 'failure'
|
||||
@@ -61,10 +61,10 @@ jobs:
|
||||
WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||
URL="${{ github.event.workflow_run.html_url }}"
|
||||
|
||||
curl -sS \
|
||||
curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
|
||||
-H "Title: ${REPO} workflow failed" \
|
||||
-H "Tags: x,warning" \
|
||||
-H "Priority: high" \
|
||||
-H "Click: ${URL}" \
|
||||
-d "${WORKFLOW} failed. Check the run for details." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}"
|
||||
"${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
|
||||
|
||||
@@ -258,8 +258,9 @@ jobs:
|
||||
while IFS= read -r -d '' file; do
|
||||
# Skip vendor, node_modules, and index.html stub files
|
||||
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
|
||||
# Check first 10 lines for JEXEC or JPATH guard
|
||||
if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
|
||||
# Scan the whole file for the JEXEC/JPATH guard: it is placed after
|
||||
# the SPDX/file-header docblock, which commonly runs past 20 lines.
|
||||
if ! grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]" "$file"; then
|
||||
echo "::error file=${file}::Missing JEXEC guard: ${file}"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
+4
-13
@@ -1,6 +1,10 @@
|
||||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
## [02.62.00] --- 2026-07-13
|
||||
|
||||
## [02.61.00] --- 2026-07-13
|
||||
|
||||
## [02.60.00] --- 2026-07-12
|
||||
|
||||
|
||||
@@ -51,16 +55,3 @@
|
||||
### Fixed
|
||||
- Admin menu / duplicate-component fallout: (a) the orphaned `com_component-mokosuitebackup` is now removed in **preflight** too, so its "Backup" menu no longer collides on install ("The alias backup is already being used"); (b) `ensureSubmenuItems()` **recreates the top-level "Backup" menu** if it was deleted, instead of giving up — so Backup no longer disappears from the admin menu (and the cPanel module / MokoSuiteClient can find it again); (c) the component menu label now uses the short constant `COM_MOKOJOOMBACKUP_SHORT`. (#213 fallout)
|
||||
- Installer no longer aborts on MySQL 8 with "This command is not supported in the prepared statement protocol yet" (error 1295). The legacy remote-column purge migration (`02.56.01.sql`) used `PREPARE`/`EXECUTE`/`DEALLOCATE`, which Joomla's installer rejects; the drop now runs in the package installer script via an INFORMATION_SCHEMA-gated plain `ALTER` (portable across MariaDB and MySQL 8). (#213 update path)
|
||||
|
||||
## [02.57.00] --- 2026-07-05
|
||||
|
||||
### Added
|
||||
- `BackupRunner` service — a single synchronous entry point for backups that unifies final status and notification gating across the pre-update, web-cron, pre-install, scheduled-task and CLI triggers (delegates to `BackupEngine`; no double-notify). Returns a normalized result including the real complete/warning/fail status. Dashboard "Backup Now" (AJAX) unchanged; installer progress popup deferred to #196. (#214)
|
||||
|
||||
### Fixed
|
||||
- Release automation: the changelog-promote step is now idempotent — it only inserts a `## [version]` header when that version isn't already present, preventing the empty, duplicated version headers that repeated/same-version builds were producing. (workflow)
|
||||
|
||||
## [02.56.11] --- 2026-07-05
|
||||
|
||||
### Fixed
|
||||
- Package `postflight` now removes the orphaned `com_component-mokosuitebackup` registration (stray extension record, admin menu items, schema rows, and `administrator/` folder) left behind by the old "Type - Name" component `<name>` — self-healing on update so affected sites reconcile to the real `com_mokosuitebackup`. (#213 follow-up)
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
|
||||
INGROUP: Template-Joomla.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 02.60.00
|
||||
VERSION: 02.63.08
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"php": ">=8.3",
|
||||
"ext-zip": "*",
|
||||
"ext-pdo": "*",
|
||||
"ext-pdo_mysql": "*",
|
||||
|
||||
Submodule source/packages/MokoSuiteClient updated: 246f7bb10c...050b05d6ef
@@ -349,6 +349,15 @@ 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_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE="Please do not close this window or switch to another window until the backup finishes."
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL="Cancel backup"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1="Cancel the backup that is in progress?"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2="Are you sure? The backup will be stopped and the update will NOT continue."
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING="Cancelling…"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED_NO_UPDATE="Backup failed — the update was not started. Retry the backup, or go back without updating."
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
|
||||
|
||||
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
|
||||
|
||||
@@ -71,6 +71,15 @@ 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_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE="Please do not close this window or switch to another window until the backup finishes."
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL="Cancel backup"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1="Cancel the backup that is in progress?"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2="Are you sure? The backup will be stopped and the update will NOT continue."
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING="Cancelling…"
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED_NO_UPDATE="Backup failed — the update was not started. Retry the backup, or go back without updating."
|
||||
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
|
||||
|
||||
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
|
||||
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
|
||||
|
||||
@@ -27,7 +27,9 @@ body {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* overflow:hidden clips the full-bleed bar to the card's rounded corners. */
|
||||
/* overflow:hidden clips the full-bleed bar to the card's rounded corners.
|
||||
The card's gray background comes from a Bootstrap utility class on the
|
||||
element itself (see the template), not a rule here. */
|
||||
.msb-runbackup .card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
display label there.
|
||||
-->
|
||||
<name>MokoSuiteBackup</name>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.60.01 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.60.02 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.60.03 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.60.04 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.60.05 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.60.06 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.61.00 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.61.01 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.61.04 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.61.05 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.61.06 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.62.00 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.62.01 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.62.02 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.62.03 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.62.04 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.62.05 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.63.01 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.63.02 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.63.05 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.63.06 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 02.63.08 — no schema changes */
|
||||
@@ -480,8 +480,8 @@ function actionPreflight(): array
|
||||
$checks[] = [
|
||||
'label' => 'PHP Version',
|
||||
'value' => PHP_VERSION,
|
||||
'ok' => version_compare(PHP_VERSION, '8.1', '>='),
|
||||
'hint' => 'Joomla 4/5 requires PHP 8.1+',
|
||||
'ok' => version_compare(PHP_VERSION, '8.3', '>='),
|
||||
'hint' => 'Joomla 6 requires PHP 8.3+',
|
||||
];
|
||||
|
||||
$checks[] = [
|
||||
|
||||
@@ -104,7 +104,7 @@ class DashboardModel extends BaseDatabaseModel
|
||||
// PHP version
|
||||
$checks[] = (object) [
|
||||
'label' => 'PHP Version',
|
||||
'status' => version_compare(PHP_VERSION, '8.1.0', '>='),
|
||||
'status' => version_compare(PHP_VERSION, '8.3.0', '>='),
|
||||
'detail' => PHP_VERSION,
|
||||
];
|
||||
|
||||
|
||||
@@ -14,13 +14,18 @@ use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
HTMLHelper::_('behavior.formvalidator');
|
||||
HTMLHelper::_('behavior.keepalive');
|
||||
HTMLHelper::_('bootstrap.modal');
|
||||
|
||||
/* Component CSS via the Web Asset Manager (declared in media/joomla.asset.json) */
|
||||
/* Component CSS. Registered in media/joomla.asset.json, but this component's WAM
|
||||
styles don't emit on the admin document (asset resolves without error yet no
|
||||
<link> renders), so attach the external stylesheet directly — reliably output
|
||||
via <jdoc:include type="styles" />. */
|
||||
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.admin');
|
||||
$this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebackup/css/admin.css');
|
||||
|
||||
$profileId = (int) $this->item->id;
|
||||
$token = Session::getFormToken();
|
||||
|
||||
@@ -86,15 +86,26 @@ $config = [
|
||||
'dashboard' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD'),
|
||||
'viewRecord' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD'),
|
||||
'leaveWarn' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING'),
|
||||
'continueUpdate' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE'),
|
||||
'cancelConfirm1' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1'),
|
||||
'cancelConfirm2' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2'),
|
||||
'cancelling' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING'),
|
||||
'failedNoUpdate' => Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED_NO_UPDATE'),
|
||||
],
|
||||
];
|
||||
|
||||
// All screen CSS loads via the Joomla Web Asset Manager (media/joomla.asset.json),
|
||||
// not an inline <style> block.
|
||||
// Screen CSS lives in an external media stylesheet (media/com_mokosuitebackup/
|
||||
// css/runbackup.css), not an inline <style> block. It is registered in
|
||||
// media/joomla.asset.json, but this component's Web Asset Manager styles do not
|
||||
// emit on the admin document (the asset resolves without error yet no <link> is
|
||||
// produced — the same is true of com_mokosuitebackup.admin), so we attach the
|
||||
// stylesheet directly to the document, which reliably renders via <jdoc:include
|
||||
// type="styles" />.
|
||||
$this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbackup');
|
||||
$this->getDocument()->addStyleSheet(Uri::root(true) . '/media/com_mokosuitebackup/css/runbackup.css');
|
||||
?>
|
||||
<div class="msb-runbackup">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card shadow-sm bg-body-secondary">
|
||||
<div class="card-body p-4 pb-3">
|
||||
<h1 class="h4 mb-1" id="msb-rb-title">
|
||||
<span class="icon-archive" aria-hidden="true"></span>
|
||||
@@ -111,14 +122,36 @@ $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbac
|
||||
</p>
|
||||
|
||||
<div id="msb-rb-phase" class="fw-bold mb-1"></div>
|
||||
<div id="msb-rb-status" class="text-muted small mb-0"><?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), ENT_QUOTES, 'UTF-8'); ?></div>
|
||||
<div id="msb-rb-status" class="mb-0"><?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), ENT_QUOTES, 'UTF-8'); ?></div>
|
||||
|
||||
<div id="msb-rb-warn" class="alert alert-warning fw-bold mt-3 mb-0" role="alert">
|
||||
<span class="icon-warning" aria-hidden="true"></span>
|
||||
<?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE'), ENT_QUOTES, 'UTF-8'); ?>
|
||||
</div>
|
||||
|
||||
<?php if ($safeReturnUrl !== '') : ?>
|
||||
<!-- Pre-update/uninstall flow: opt in to auto-continue, or get a manual "Continue" button on completion. -->
|
||||
<div class="form-check mt-3" id="msb-rb-autocontinue-wrap">
|
||||
<input class="form-check-input" type="checkbox" id="msb-rb-autocontinue" checked>
|
||||
<label class="form-check-label" for="msb-rb-autocontinue">
|
||||
<?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE'), ENT_QUOTES, 'UTF-8'); ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="msb-rb-actions" class="d-none mt-3">
|
||||
<button type="button" id="msb-rb-retry" class="btn btn-primary d-none"></button>
|
||||
<a href="#" id="msb-rb-record" class="btn btn-primary d-none"></a>
|
||||
<a href="#" id="msb-rb-record" class="btn btn-primary d-none" target="_blank" rel="noopener"></a>
|
||||
<a href="#" id="msb-rb-continue" class="btn btn-warning d-none"></a>
|
||||
<a href="<?php echo $dashboardUrl; ?>" id="msb-rb-dashboard" class="btn btn-secondary d-none"></a>
|
||||
</div>
|
||||
|
||||
<!-- Cancel is available while the backup is running; requires a double confirmation. -->
|
||||
<div id="msb-rb-cancel-wrap" class="mt-3">
|
||||
<button type="button" id="msb-rb-cancel" class="btn btn-outline-danger">
|
||||
<?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL'), ENT_QUOTES, 'UTF-8'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Full-bleed progress bar, edge-to-edge along the bottom of the card. -->
|
||||
@@ -135,6 +168,7 @@ $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbac
|
||||
var CFG = <?php echo json_encode($config); ?>;
|
||||
var L = CFG.labels || {};
|
||||
var running = false;
|
||||
var cancelled = false;
|
||||
var lastRecordId = 0;
|
||||
|
||||
var el = {
|
||||
@@ -142,14 +176,46 @@ $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbac
|
||||
track: document.querySelector('.msb-rb-progress'),
|
||||
phase: document.getElementById('msb-rb-phase'),
|
||||
status: document.getElementById('msb-rb-status'),
|
||||
warn: document.getElementById('msb-rb-warn'),
|
||||
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')
|
||||
dashboard: document.getElementById('msb-rb-dashboard'),
|
||||
autocontinue: document.getElementById('msb-rb-autocontinue'),
|
||||
autocontinueWrap: document.getElementById('msb-rb-autocontinue-wrap'),
|
||||
cancel: document.getElementById('msb-rb-cancel'),
|
||||
cancelWrap: document.getElementById('msb-rb-cancel-wrap')
|
||||
};
|
||||
|
||||
function hideCancel() { if (el.cancelWrap) { el.cancelWrap.classList.add('d-none'); } }
|
||||
|
||||
/* Cancel the running backup — requires DOUBLE confirmation. Stops the step
|
||||
loop, tells the server to cancel the record, and leaves to the dashboard
|
||||
WITHOUT continuing any pending update. */
|
||||
if (el.cancel) {
|
||||
el.cancel.addEventListener('click', function () {
|
||||
if (!running || cancelled) { return; }
|
||||
if (!window.confirm(L.cancelConfirm1 || 'Cancel the backup that is in progress?')) { return; }
|
||||
if (!window.confirm(L.cancelConfirm2 || 'Are you sure? The backup will be stopped and the update will NOT continue.')) { return; }
|
||||
|
||||
cancelled = true;
|
||||
running = false; /* also disarms the leave-warning */
|
||||
el.cancel.disabled = true;
|
||||
if (el.warn) { el.warn.classList.add('d-none'); }
|
||||
setStatus(L.cancelling || 'Cancelling…');
|
||||
|
||||
var go = function () { window.location.href = CFG.dashboardUrl; };
|
||||
/* Best-effort server-side cancel of the record, then leave. */
|
||||
if (lastRecordId > 0) {
|
||||
post({ task: 'ajax.cancelBackup', id: lastRecordId }).then(go, go);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setBar(pct, striped) {
|
||||
pct = Math.max(0, Math.min(100, parseInt(pct, 10) || 0));
|
||||
el.bar.style.width = pct + '%';
|
||||
@@ -185,6 +251,8 @@ $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbac
|
||||
el.bar.classList.add('is-success');
|
||||
setBar(100, false);
|
||||
el.title.textContent = L.complete || 'Backup complete';
|
||||
if (el.warn) { el.warn.classList.add('d-none'); }
|
||||
hideCancel();
|
||||
|
||||
/* Set the one-shot skip flag for THIS action so the following
|
||||
server-side onExtensionBefore(Update|Uninstall) backup is skipped once
|
||||
@@ -193,10 +261,30 @@ $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbac
|
||||
try { await post({ task: 'ajax.preupdateAck', action: CFG.action || '' }); } 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);
|
||||
/* Pre-update / pre-uninstall flow. If "auto-continue" is ticked
|
||||
(default) hand back to Joomla automatically; otherwise stop here
|
||||
and offer a link to the new backup record plus a manual
|
||||
"Continue the update" button. */
|
||||
if (el.autocontinueWrap) { el.autocontinueWrap.classList.add('d-none'); }
|
||||
|
||||
var autoContinue = !el.autocontinue || el.autocontinue.checked;
|
||||
|
||||
if (autoContinue) {
|
||||
setStatus(L.continuing || 'Continuing…');
|
||||
window.setTimeout(function () { window.location.href = CFG.returnUrl; }, 1200);
|
||||
} else {
|
||||
setStatus(L.complete || 'Backup complete');
|
||||
showActions();
|
||||
|
||||
if (lastRecordId > 0 && CFG.recordUrl) {
|
||||
showBtn(el.record, L.viewRecord || 'View backup record',
|
||||
CFG.recordUrl.replace('__MSBID__', String(lastRecordId)));
|
||||
}
|
||||
|
||||
el.continue.classList.remove('btn-warning');
|
||||
el.continue.classList.add('btn-success');
|
||||
showBtn(el.continue, L.continueUpdate || 'Continue the update', CFG.returnUrl);
|
||||
}
|
||||
} else {
|
||||
/* Manual "Backup Now": offer to view the record that was just made. */
|
||||
setStatus(L.complete || 'Backup complete');
|
||||
@@ -215,30 +303,34 @@ $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbac
|
||||
running = false;
|
||||
el.bar.classList.remove('is-animated');
|
||||
el.bar.classList.add('is-danger');
|
||||
if (el.warn) { el.warn.classList.add('d-none'); }
|
||||
el.title.textContent = L.failed || 'Backup failed';
|
||||
setStatus(message || 'Backup failed');
|
||||
/* Pre-update/uninstall flow: the backup failed, so the update must NOT
|
||||
run. We deliberately do NOT offer a "continue without backup" option
|
||||
here — the only ways forward are to retry the backup or go back. */
|
||||
setStatus(CFG.returnUrl ? (L.failedNoUpdate || 'Backup failed — the update was not started. Retry the backup, or go back without updating.') : (message || 'Backup failed'));
|
||||
hideCancel();
|
||||
showActions();
|
||||
|
||||
el.retry.textContent = L.retry || 'Retry';
|
||||
el.retry.classList.remove('d-none');
|
||||
el.retry.onclick = function () {
|
||||
el.retry.classList.add('d-none');
|
||||
el.continue.classList.add('d-none');
|
||||
el.bar.classList.remove('is-danger');
|
||||
el.bar.classList.add('is-animated');
|
||||
run();
|
||||
};
|
||||
|
||||
if (CFG.returnUrl) {
|
||||
showBtn(el.continue, L.continue || 'Continue without backup', CFG.returnUrl);
|
||||
}
|
||||
showBtn(el.dashboard, L.dashboard || 'Back to dashboard', CFG.dashboardUrl);
|
||||
}
|
||||
|
||||
async function run() {
|
||||
if (running) { return; }
|
||||
running = true;
|
||||
cancelled = false;
|
||||
el.actions.classList.add('d-none');
|
||||
if (el.cancelWrap) { el.cancelWrap.classList.remove('d-none'); }
|
||||
if (el.cancel) { el.cancel.disabled = false; }
|
||||
setBar(0, true);
|
||||
setPhase('');
|
||||
setStatus(L.starting || 'Starting backup…');
|
||||
@@ -256,7 +348,7 @@ $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbac
|
||||
if (init.record_id) { lastRecordId = init.record_id; }
|
||||
|
||||
var done = false;
|
||||
while (!done) {
|
||||
while (!done && !cancelled) {
|
||||
var step = await post({ task: 'ajax.step', session_id: init.session_id });
|
||||
|
||||
if (!step || step.error) {
|
||||
@@ -270,16 +362,27 @@ $this->getDocument()->getWebAssetManager()->useStyle('com_mokosuitebackup.runbac
|
||||
done = step.done || false;
|
||||
}
|
||||
|
||||
/* User cancelled mid-flight — the click handler already stops the
|
||||
loop and navigates away, so do not fall through to completion. */
|
||||
if (cancelled) { return; }
|
||||
|
||||
running = false;
|
||||
await onComplete();
|
||||
} catch (err) {
|
||||
if (cancelled) { return; }
|
||||
onError(err && err.message ? err.message : String(err));
|
||||
}
|
||||
}
|
||||
|
||||
/* Warn before leaving while a backup is mid-flight. */
|
||||
/* Warn before closing/leaving the window while a backup is mid-flight.
|
||||
Return the message too — some browsers need it, others show a generic
|
||||
prompt, but any non-empty value triggers the confirmation dialog. */
|
||||
window.addEventListener('beforeunload', function (e) {
|
||||
if (running) { e.preventDefault(); e.returnValue = L.leaveWarn || ''; }
|
||||
if (!running) { return undefined; }
|
||||
var msg = L.leaveWarn || 'A backup is in progress. Leaving now will cancel it.';
|
||||
e.preventDefault();
|
||||
e.returnValue = msg;
|
||||
return msg;
|
||||
});
|
||||
|
||||
if (CFG.autostart) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-->
|
||||
<extension type="module" client="administrator" method="upgrade">
|
||||
<name>Module - MokoSuiteBackup - cPanel</name>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-23</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="actionlog" method="upgrade">
|
||||
<name>Action Log - MokoSuiteBackup</name>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="console" method="upgrade">
|
||||
<name>Console - MokoSuiteBackup</name>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>Content - MokoSuiteBackup</name>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="quickicon" method="upgrade">
|
||||
<name>Quick Icon - MokoSuiteBackup</name>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
+1
@@ -14,3 +14,4 @@ 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…"
|
||||
PLG_SYSTEM_MOKOSUITEBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
|
||||
|
||||
+1
@@ -14,3 +14,4 @@ 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…"
|
||||
PLG_SYSTEM_MOKOSUITEBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
|
||||
|
||||
@@ -101,6 +101,31 @@
|
||||
window.location.href = target;
|
||||
}, true);
|
||||
|
||||
/* Full-screen "update is running" overlay shown while the re-fired update
|
||||
POST processes, so the user knows it is working and not to close/switch. */
|
||||
function showUpdateOverlay() {
|
||||
if (document.getElementById('msb-update-overlay')) { return; }
|
||||
|
||||
var o = document.createElement('div');
|
||||
o.id = 'msb-update-overlay';
|
||||
o.setAttribute('role', 'status');
|
||||
o.style.cssText = 'position:fixed;inset:0;z-index:2147483647;background:#000;color:#fff;'
|
||||
+ 'display:flex;flex-direction:column;align-items:center;justify-content:center;'
|
||||
+ 'text-align:center;padding:2rem;font-size:1.15rem;font-weight:600;';
|
||||
|
||||
var spin = document.createElement('span');
|
||||
spin.className = 'spinner-border mb-3';
|
||||
spin.setAttribute('aria-hidden', 'true');
|
||||
|
||||
var msg = document.createElement('div');
|
||||
msg.textContent = (cfg && cfg.updateRunningText)
|
||||
|| 'Backup complete. The update is now running — please wait and do not close this window…';
|
||||
|
||||
o.appendChild(spin);
|
||||
o.appendChild(msg);
|
||||
document.body.appendChild(o);
|
||||
}
|
||||
|
||||
/* On return from the backup screen: restore the selection and re-fire. */
|
||||
function resume() {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
@@ -131,20 +156,40 @@
|
||||
if (box) { box.checked = true; }
|
||||
});
|
||||
|
||||
/* Joomla's list-check reads boxchecked — set it so the re-fire passes. */
|
||||
/* Restore boxchecked AND fire its `change` event. Joomla's toolbar web
|
||||
component (joomla-toolbar-button) keeps a list-selection button
|
||||
DISABLED until boxchecked changes via a change event — setting the
|
||||
value alone leaves the button disabled, so executeTask() would no-op
|
||||
and the update never submits (it just returns to the list). */
|
||||
var bc = form.querySelector('input[name="boxchecked"]');
|
||||
if (bc) { bc.value = (data.cids || []).length; }
|
||||
if (bc) {
|
||||
bc.value = (data.cids || []).length;
|
||||
bc.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
|
||||
window.__msbResuming = true;
|
||||
|
||||
var btn = document.querySelector('[task="' + data.task + '"]');
|
||||
/* Show a full-screen "update is running" overlay so the user gets clear
|
||||
feedback that the update is proceeding while the re-fired POST runs.
|
||||
It persists until the page navigates away (the update finishes and
|
||||
redirects), matching the black full-screen backup screen. */
|
||||
showUpdateOverlay();
|
||||
|
||||
if (btn) {
|
||||
window.setTimeout(function () { btn.click(); }, 300);
|
||||
} else if (window.Joomla && typeof Joomla.submitbutton === 'function') {
|
||||
Joomla.submitbutton(data.task);
|
||||
}
|
||||
/* Re-fire the toolbar action. The `task` attribute is on the OUTER
|
||||
<joomla-toolbar-button>, but its click handler is bound to the INNER
|
||||
<button>/<a>, so click that inner element (clicking the wrapper does
|
||||
nothing). Fall back to Joomla.submitbutton if the button isn't found. */
|
||||
window.setTimeout(function () {
|
||||
var wc = document.querySelector('[task="' + data.task + '"]');
|
||||
var inner = wc ? (wc.querySelector('button, a') || wc) : null;
|
||||
|
||||
if (inner) {
|
||||
inner.click();
|
||||
} else if (window.Joomla && typeof Joomla.submitbutton === 'function') {
|
||||
Joomla.submitbutton(data.task);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>System - MokoSuiteBackup</name>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -15,6 +15,7 @@ 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;
|
||||
@@ -243,6 +244,7 @@ final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
|
||||
'backupBeforeUpdate' => $view === 'update' && (bool) $beforeUpdate,
|
||||
'backupBeforeUninstall' => $view === 'manage' && (bool) $beforeUninst,
|
||||
'recentBackup' => false,
|
||||
'updateRunningText' => Text::_('PLG_SYSTEM_MOKOSUITEBACKUP_UPDATE_RUNNING'),
|
||||
]);
|
||||
|
||||
$doc->addScript(Uri::root(true) . '/media/plg_system_mokosuitebackup/js/installer-backup.js', [], ['defer' => true]);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="task" method="upgrade">
|
||||
<name>Task - MokoSuiteBackup</name>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="webservices" method="upgrade">
|
||||
<name>Web Services - MokoSuiteBackup</name>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<extension type="package" method="upgrade">
|
||||
<name>Package - MokoSuiteBackup</name>
|
||||
<packagename>mokosuitebackup</packagename>
|
||||
<version>02.60.00</version>
|
||||
<version>02.63.08</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
Reference in New Issue
Block a user