From ca7ab8f36369c67d0ed01d5abf517a2ec3bb8160 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 6 Jun 2026 17:52:42 -0500 Subject: [PATCH] feat: JS placeholder resolution in FolderPicker for portable profiles FolderPickerField now resolves [site_name], [host], [date], etc. in JavaScript before browsing directories, and reverse-replaces actual values back to placeholders when user selects a path. This makes profiles portable across sites. Default placeholder hint uses [host] for directory paths. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/Field/FolderPickerField.php | 81 ++++++++++++++++--- 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/source/packages/com_mokojoombackup/src/Field/FolderPickerField.php b/source/packages/com_mokojoombackup/src/Field/FolderPickerField.php index 3cb06034..f0ac4d2e 100644 --- a/source/packages/com_mokojoombackup/src/Field/FolderPickerField.php +++ b/source/packages/com_mokojoombackup/src/Field/FolderPickerField.php @@ -12,6 +12,7 @@ namespace Joomla\Component\MokoJoomBackup\Administrator\Field; defined('_JEXEC') or die; +use Joomla\CMS\Factory; use Joomla\CMS\Form\FormField; use Joomla\CMS\Language\Text; @@ -35,13 +36,43 @@ class FolderPickerField extends FormField $absPath = $rawValue; } - // If path contains placeholders, show info status instead of checking disk + // Build placeholder map for JS resolution + $hostname = preg_replace('/[^a-zA-Z0-9._-]/', '', $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? php_uname('n')); + $siteName = ''; + + try { + $siteName = Factory::getApplication()->get('sitename', ''); + } catch (\Throwable $e) { + // fallback + } + + $sanitizedSiteName = preg_replace('/[^a-zA-Z0-9._-]/', '', str_replace(' ', '-', trim($siteName))); + + $placeholders = [ + '[host]' => $hostname, + '[site_name]' => $sanitizedSiteName ?: 'joomla', + '[profile_id]' => '1', + '[profile_name]' => 'default', + '[type]' => 'full', + '[year]' => date('Y'), + '[month]' => date('m'), + '[day]' => date('d'), + '[date]' => date('Ymd'), + ]; + + $placeholdersJson = json_encode($placeholders); + + // Resolve placeholders for the status display + $resolvedPath = str_replace(array_keys($placeholders), array_values($placeholders), $absPath); $hasPlaceholders = preg_match('/\[.+\]/', $absPath); if ($hasPlaceholders) { - $statusClass = 'text-info'; - $statusIcon = 'icon-info-circle'; + $exists = is_dir($resolvedPath); + $statusClass = $exists ? 'text-success' : 'text-info'; + $statusIcon = $exists ? 'icon-publish' : 'icon-info-circle'; $statusText = Text::_('COM_MOKOJOOMBACKUP_FOLDER_PLACEHOLDER'); + $resolvedSafe = htmlspecialchars($resolvedPath, ENT_QUOTES, 'UTF-8'); + $statusDetail = "{$statusText}: {$resolvedSafe}"; } else { $exists = is_dir($absPath); $statusClass = $exists ? 'text-success' : 'text-danger'; @@ -49,24 +80,24 @@ class FolderPickerField extends FormField $statusText = $exists ? Text::_('COM_MOKOJOOMBACKUP_FOLDER_EXISTS') : Text::_('COM_MOKOJOOMBACKUP_FOLDER_NOT_FOUND'); + $absPathSafe = htmlspecialchars($absPath, ENT_QUOTES, 'UTF-8'); + $statusDetail = "{$statusText}: {$absPathSafe}"; } - $absPathSafe = htmlspecialchars($absPath, ENT_QUOTES, 'UTF-8'); - return << + placeholder="/home/user/backups/[host] or administrator/components/com_mokojoombackup/backups" /> -
+
- {$statusText}: {$absPathSafe} + {$statusDetail}