diff --git a/source/packages/com_mokosuitebackup/src/Controller/BackupsController.php b/source/packages/com_mokosuitebackup/src/Controller/BackupsController.php
index 5e1e9e6..ef63aeb 100644
--- a/source/packages/com_mokosuitebackup/src/Controller/BackupsController.php
+++ b/source/packages/com_mokosuitebackup/src/Controller/BackupsController.php
@@ -15,6 +15,7 @@ defined('_JEXEC') or die;
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;
@@ -34,7 +35,14 @@ class BackupsController extends AdminController
*/
public function start(): void
{
- $this->checkToken();
+ /* Accept token from both GET (profile Run button) and POST (backup form).
+ Joomla's checkToken() throws on failure, so try GET first. */
+ if (!Session::checkToken('get') && !Session::checkToken('post')) {
+ $this->setMessage(Text::_('JINVALID_TOKEN_NOTICE'), 'error');
+ $this->setRedirect(Route::_('index.php?option=com_mokosuitebackup&view=backups', false));
+
+ return;
+ }
if (!$this->app->getIdentity()->authorise('mokosuitebackup.backup.run', 'com_mokosuitebackup')) {
$this->setMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error');
diff --git a/source/packages/com_mokosuitebackup/src/Field/FolderPickerField.php b/source/packages/com_mokosuitebackup/src/Field/FolderPickerField.php
index 6ecb23b..4177a56 100644
--- a/source/packages/com_mokosuitebackup/src/Field/FolderPickerField.php
+++ b/source/packages/com_mokosuitebackup/src/Field/FolderPickerField.php
@@ -117,6 +117,8 @@ class FolderPickerField extends FormField
{$statusDetail}
+
+
The default backup directory is inside the web root. Backup archives may be directly downloadable if .htaccess is not supported. For better security, use a path outside the web root.
@@ -284,8 +286,54 @@ class FolderPickerField extends FormField
});
}
+ /* Show which placeholders are in use and their resolved values */
+ var resolvedDiv = document.getElementById(fieldId + '_resolved');
+
+ function updateResolvedDisplay() {
+ while (resolvedDiv.firstChild) resolvedDiv.removeChild(resolvedDiv.firstChild);
+ var val = input.value || '';
+ var found = false;
+
+ for (var key in placeholders) {
+ if (val.indexOf(key) !== -1 && placeholders[key]) {
+ found = true;
+ var badge = document.createElement('span');
+ badge.className = 'badge bg-light text-dark border me-1 mb-1';
+ badge.style.fontSize = '0.75rem';
+ badge.style.fontFamily = 'monospace';
+
+ var keySpan = document.createElement('strong');
+ keySpan.textContent = key;
+ badge.appendChild(keySpan);
+
+ badge.appendChild(document.createTextNode(' = '));
+
+ var valSpan = document.createElement('span');
+ valSpan.className = 'text-primary';
+ valSpan.textContent = placeholders[key];
+ badge.appendChild(valSpan);
+
+ resolvedDiv.appendChild(badge);
+ }
+ }
+
+ if (found) {
+ var fullResolved = document.createElement('div');
+ fullResolved.className = 'mt-1';
+ var arrow = document.createElement('span');
+ arrow.className = 'text-muted';
+ arrow.textContent = 'Resolves to: ';
+ fullResolved.appendChild(arrow);
+ var code = document.createElement('code');
+ code.textContent = resolve(val);
+ fullResolved.appendChild(code);
+ resolvedDiv.appendChild(fullResolved);
+ }
+ }
+
input.addEventListener('input', function() {
clearTimeout(checkTimer);
+ updateResolvedDisplay();
checkTimer = setTimeout(checkDirPermissions, 400);
});
@@ -399,6 +447,7 @@ class FolderPickerField extends FormField
// Run initial check on page load
setDefaultDirWarning();
+ updateResolvedDisplay();
checkDirPermissions();
})();