Compare commits

..

9 Commits

Author SHA1 Message Date
mokogitea-actions[bot] 4b6d80a248 chore(version): pre-release bump to 02.58.34-dev [skip ci] 2026-07-12 21:42:42 +00:00
jmiller 4d476fde5d @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
style(runbackup): drop max-width/margin so the card spans full width

The backup screen was centred in a 720px column; unset max-width and the
auto margins so it fills the available width. Keeps the horizontal padding.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 16:41:47 -05:00
mokogitea-actions[bot] 2c12fdc176 chore(version): pre-release bump to 02.58.32-dev [skip ci] 2026-07-12 21:14:11 +00:00
mokogitea-actions[bot] 39495e5b12 chore(version): pre-release bump to 02.58.31-dev [skip ci] 2026-07-12 21:08:47 +00:00
jmiller 44ed98d3c3 Merge pull request 'fix: reliably intercept extension update/uninstall + polish backup screen' (#245) from fix/extension-intercept-ui into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
2026-07-12 21:08:34 +00:00
mokogitea-actions[bot] 9aab873750 chore(version): pre-release bump to 02.58.30-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been cancelled
2026-07-12 21:07:56 +00:00
jmiller 4bc8b47d05 @
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
fix: reliably intercept extension update/uninstall + polish backup screen

The full-screen pre-action backup did not fire for extension
updates/uninstalls because Joomla 6 renders the com_installer toolbar
Update/Uninstall buttons as `<button task="update.update">` web
components, not inline onclick="Joomla.submitbutton(...)". Wrapping
Joomla.submitbutton never caught them.

installer-backup.js now uses a capture-phase document click listener on
any `[task]` element matching update.update / manage.remove(/uninstall),
so it runs before the toolbar web component. It captures the checked
cid[] selection, runs the full-screen backup, and on return restores the
selection, sets the boxchecked list-guard field, and re-fires the real
toolbar button with __msbResuming set so it is not re-intercepted.

Also polishes the runbackup screen: the progress bar is now a full-bleed
strip along the bottom edge of the card (edge-to-edge, clipped to the
rounded corners) instead of a boxed bar crowding the status text, and it
is coloured from the Atum template CSS custom properties
(--template-link-color / --template-special-color / --template-success-color
/ --template-danger-color) with Bootstrap and literal fallbacks.

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
@
2026-07-12 16:07:36 -05:00
mokogitea-actions[bot] d6078ef528 chore(version): pre-release bump to 02.58.29-dev [skip ci] 2026-07-12 20:51:25 +00:00
jmiller 3e4bb03165 Merge pull request 'feat: seamless auto-continue on Joomla Update after pre-update backup' (#244) from fix/joomlaupdate-seamless into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
2026-07-12 20:50:59 +00:00
20 changed files with 157 additions and 109 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: mokocli.Automation
# VERSION: 02.58.28
# VERSION: 02.58.34
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md
VERSION: 02.58.28
VERSION: 02.58.34
BRIEF: Security vulnerability reporting and handling policy
-->
@@ -17,7 +17,7 @@
display label there.
-->
<name>MokoSuiteBackup</name>
<version>02.58.28</version>
<version>02.58.34</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1 +0,0 @@
/* 02.58.28 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.29 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.30 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.31 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.32 — no schema changes */
@@ -0,0 +1 @@
/* 02.58.34 — no schema changes */
@@ -80,9 +80,55 @@ $config = [
],
];
?>
<div class="msb-runbackup" style="max-width:640px;margin:3rem auto;padding:0 1rem;">
<style>
/* Colours are pulled from the Atum admin template's own CSS custom
properties so the screen matches whatever theme the site runs, with
Bootstrap and then a literal as progressive fallbacks. */
.msb-runbackup {
--msb-accent: var(--template-link-color, var(--bs-link-color, #2a69b8));
--msb-accent2: var(--template-special-color, var(--msb-accent));
--msb-success: var(--template-success-color, var(--bs-success, #2f7d32));
--msb-danger: var(--template-danger-color, var(--bs-danger, #c52128));
--msb-track: var(--template-bg-light, var(--bs-gray-200, #edf0f5));
padding: 0 1rem;
}
/* overflow:hidden clips the full-bleed bar to the card's rounded corners. */
.msb-runbackup .card { overflow: hidden; }
.msb-rb-progress {
height: 2.5rem;
background: var(--msb-track);
overflow: hidden;
}
.msb-rb-bar {
height: 100%;
width: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
font-size: .95rem;
white-space: nowrap;
background-color: var(--msb-accent);
background-image: linear-gradient(90deg, var(--msb-accent), var(--msb-accent2));
transition: width .35s ease;
}
.msb-rb-bar.is-animated::after {
content: "";
position: absolute;
inset: 0;
background-image: linear-gradient(45deg, rgba(255,255,255,.18) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.18) 50%, rgba(255,255,255,.18) 75%, transparent 75%, transparent);
background-size: 1rem 1rem;
animation: msb-rb-stripes 1s linear infinite;
}
.msb-rb-bar { position: relative; }
.msb-rb-bar.is-success { background-image: none; background-color: var(--msb-success); }
.msb-rb-bar.is-danger { background-image: none; background-color: var(--msb-danger); }
@keyframes msb-rb-stripes { from { background-position: 1rem 0; } to { background-position: 0 0; } }
</style>
<div class="msb-runbackup">
<div class="card shadow-sm">
<div class="card-body p-4">
<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>
<?php echo Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE'); ?>
@@ -97,21 +143,21 @@ $config = [
); ?>
</p>
<div class="progress mb-3" style="height:1.75rem;">
<div id="msb-rb-bar" class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width:0;" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">0%</div>
</div>
<div id="msb-rb-phase" class="fw-bold mb-1"></div>
<div id="msb-rb-status" class="text-muted small mb-3"><?php echo htmlspecialchars(Text::_('COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING'), ENT_QUOTES, 'UTF-8'); ?></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-actions" class="d-none">
<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-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>
</div>
<!-- Full-bleed progress bar, edge-to-edge along the bottom of the card. -->
<div class="msb-rb-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div id="msb-rb-bar" class="msb-rb-bar is-animated" style="width:0;">0%</div>
</div>
</div>
</div>
@@ -126,6 +172,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 +187,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 +214,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 +245,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 +256,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();
};
@@ -8,7 +8,7 @@
-->
<extension type="module" client="administrator" method="upgrade">
<name>Module - MokoSuiteBackup - cPanel</name>
<version>02.58.28</version>
<version>02.58.34</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.58.28</version>
<version>02.58.34</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.58.28</version>
<version>02.58.34</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.58.28</version>
<version>02.58.34</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.58.28</version>
<version>02.58.34</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -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 (`<button task="update.update">`), NOT inline
* onclick="Joomla.submitbutton(...)", so wrapping Joomla.submitbutton is
* unreliable. Instead we add a CAPTURE-phase click listener on any element
* carrying a matching `task` attribute — it runs before the toolbar web
* component, so we can stop it, run the full-screen backup, and on return
* restore the checked cid[] selection and re-fire the button.
*
* 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.
* The full-screen page arms the pre-action throttle on success, so the
* server-side onExtensionBefore(Update|Uninstall) backup is then skipped.
*
* Config: Joomla.getOptions('plg_system_mokosuitebackup.installer').
*/
@@ -28,26 +28,65 @@
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; }
if (cfg.backupBeforeUninstall) { TASKS['manage.remove'] = true; TASKS['manage.uninstall'] = 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);
});
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() {
/* Intercept the toolbar Update/Uninstall click in the CAPTURE phase, before
the toolbar web component's own handler runs. */
document.addEventListener('click', function (e) {
if (window.__msbResuming || cfg.recentBackup) {
return;
}
var node = e.target;
var btn = (node && node.closest) ? node.closest('[task]') : null;
if (!btn) {
return;
}
var task = btn.getAttribute('task');
if (!TASKS[task]) {
return;
}
var cids = checkedCids();
/* Nothing selected — let Joomla show its own "please select" notice. */
if (!cids.length) {
return;
}
e.preventDefault();
e.stopImmediatePropagation();
try {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ task: task, cids: cids }));
} catch (x) {}
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));
}, true);
/* On return from the backup screen: restore the selection and re-fire. */
function resume() {
var params = new URLSearchParams(window.location.search);
if (params.get('msb_resume') !== '1') {
@@ -55,15 +94,14 @@
}
var raw = null;
try { raw = sessionStorage.getItem(STORAGE_KEY); } catch (e) {}
try { sessionStorage.removeItem(STORAGE_KEY); } catch (e) {}
try { raw = sessionStorage.getItem(STORAGE_KEY); sessionStorage.removeItem(STORAGE_KEY); } catch (x) {}
if (!raw) {
return;
}
var data;
try { data = JSON.parse(raw); } catch (e) { data = null; }
try { data = JSON.parse(raw); } catch (x) { data = null; }
if (!data || !data.task) {
return;
@@ -71,72 +109,31 @@
var form = document.getElementById('adminForm');
if (!form) {
return;
if (form) {
(data.cids || []).forEach(function (id) {
var box = form.querySelector('input[name="cid[]"][value="' + id + '"]');
if (box) { box.checked = true; }
});
/* Joomla's list-check reads boxchecked — set it so the re-fire passes. */
var bc = form.querySelector('input[name="boxchecked"]');
if (bc) { bc.value = (data.cids || []).length; }
}
(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') {
var btn = document.querySelector('[task="' + data.task + '"]');
if (btn) {
window.setTimeout(function () { btn.click(); }, 300);
} else 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;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', resume);
} else {
resume();
}
/* Wrap as early as possible, and again once the DOM/core JS is ready. */
wrapSubmit();
document.addEventListener('DOMContentLoaded', function () {
wrapSubmit();
resumeIfNeeded();
});
})();
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteBackup</name>
<version>02.58.28</version>
<version>02.58.34</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="task" method="upgrade">
<name>Task - MokoSuiteBackup</name>
<version>02.58.28</version>
<version>02.58.34</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.58.28</version>
<version>02.58.34</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+1 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuiteBackup</name>
<packagename>mokosuitebackup</packagename>
<version>02.58.28</version>
<version>02.58.34</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>