|
|
|
@@ -96,7 +96,7 @@ class FolderPickerField extends FormField
|
|
|
|
|
<span class="icon-folder-open" aria-hidden="true"></span>
|
|
|
|
|
Browse
|
|
|
|
|
</button>
|
|
|
|
|
<button type="button" class="btn btn-outline-info" data-bs-toggle="modal" data-bs-target="#{$id}_helpModal" title="Available placeholders">
|
|
|
|
|
<button type="button" class="btn btn-outline-info" id="{$id}_helpBtn" title="Help — placeholders, paths, and examples">
|
|
|
|
|
<span class="icon-question-circle" aria-hidden="true"></span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
@@ -124,36 +124,112 @@ class FolderPickerField extends FormField
|
|
|
|
|
The default backup directory is inside the web root. Backup archives may be directly downloadable if <code>.htaccess</code> is not supported. For better security, use a path outside the web root.
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal fade" id="{$id}_helpModal" tabindex="-1" aria-labelledby="{$id}_helpLabel" aria-hidden="true">
|
|
|
|
|
<div class="modal-dialog">
|
|
|
|
|
<div class="modal-dialog modal-lg">
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
<h5 class="modal-title" id="{$id}_helpLabel">Backup Directory Placeholders</h5>
|
|
|
|
|
<h5 class="modal-title" id="{$id}_helpLabel">Backup Directory — Help</h5>
|
|
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
<p>Use these placeholders in the backup directory path. They are resolved at backup time.</p>
|
|
|
|
|
|
|
|
|
|
<h6 class="text-primary">How Path Resolution Works</h6>
|
|
|
|
|
<p>The backup directory path is resolved at backup time. You can use <strong>absolute paths</strong>, <strong>relative paths</strong>, or <strong>placeholder paths</strong>.</p>
|
|
|
|
|
|
|
|
|
|
<div class="card mb-3">
|
|
|
|
|
<div class="card-header fw-bold">Absolute Paths</div>
|
|
|
|
|
<div class="card-body py-2">
|
|
|
|
|
<p class="mb-1">Start with <code>/</code> (Linux) or a drive letter (Windows). Used as-is.</p>
|
|
|
|
|
<ul class="mb-0">
|
|
|
|
|
<li><code>/home/user/backups</code> — Fixed path on the server</li>
|
|
|
|
|
<li><code>/var/backups/joomla</code> — System backup directory</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="card mb-3">
|
|
|
|
|
<div class="card-header fw-bold">Relative Paths</div>
|
|
|
|
|
<div class="card-body py-2">
|
|
|
|
|
<p class="mb-1">Paths that do <strong>not</strong> start with <code>/</code> are resolved relative to the Joomla root directory, using the same conventions as URL paths:</p>
|
|
|
|
|
<table class="table table-sm mb-2">
|
|
|
|
|
<thead><tr><th>Path</th><th>Meaning</th><th>Resolves To</th></tr></thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr><td><code>backups</code></td><td>Subdirectory of Joomla root</td><td><code>{$jRoot}/backups</code></td></tr>
|
|
|
|
|
<tr><td><code>./backups</code></td><td>Same as above (explicit current dir)</td><td><code>{$jRoot}/backups</code></td></tr>
|
|
|
|
|
<tr><td><code>../backups</code></td><td>One level <strong>above</strong> Joomla root</td><td>Parent of <code>{$jRoot}</code></td></tr>
|
|
|
|
|
<tr><td><code>../../backups</code></td><td>Two levels above Joomla root</td><td>Grandparent of <code>{$jRoot}</code></td></tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
<div class="alert alert-warning py-1 px-2 mb-0" style="font-size:0.85rem;">
|
|
|
|
|
<strong>Warning:</strong> Relative paths that stay inside the web root may expose backup files to direct download if .htaccess is not supported. Use <code>../</code> or <code>[HOME]</code> to store backups outside the web root.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="card mb-3">
|
|
|
|
|
<div class="card-header fw-bold">Placeholder Paths (Recommended)</div>
|
|
|
|
|
<div class="card-body py-2">
|
|
|
|
|
<p class="mb-1">Use <code>[PLACEHOLDER]</code> tokens that are replaced with actual values at backup time. This makes paths <strong>portable</strong> across servers.</p>
|
|
|
|
|
<ul class="mb-0">
|
|
|
|
|
<li><code>[HOME]/backups</code> — User's home directory + /backups</li>
|
|
|
|
|
<li><code>[HOME]/[HOST]/backups</code> — Per-site subdirectory under home</li>
|
|
|
|
|
<li><code>[DEFAULT_DIR]</code> — Joomla's default backup directory</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<h6 class="text-primary mt-3">Available Placeholders</h6>
|
|
|
|
|
<table class="table table-sm table-striped">
|
|
|
|
|
<thead><tr><th>Placeholder</th><th>Description</th><th>Example</th></tr></thead>
|
|
|
|
|
<thead><tr><th>Placeholder</th><th>Description</th><th>Current Value</th></tr></thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr><td><code>[HOME]</code></td><td>Home directory of the server user</td><td><code>{$placeholders['[HOME]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[DEFAULT_DIR]</code></td><td>Default backup directory (inside web root)</td><td><code>{$placeholders['[DEFAULT_DIR]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[HOST]</code></td><td>Server hostname</td><td><code>{$placeholders['[HOST]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[SITE_NAME]</code></td><td>Joomla site name</td><td><code>{$placeholders['[SITE_NAME]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[DATE]</code></td><td>Date (Ymd)</td><td><code>{$placeholders['[DATE]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[YEAR]</code></td><td>Four-digit year</td><td><code>{$placeholders['[YEAR]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[MONTH]</code></td><td>Two-digit month</td><td><code>{$placeholders['[MONTH]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[DAY]</code></td><td>Two-digit day</td><td><code>{$placeholders['[DAY]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[PROFILE_ID]</code></td><td>Backup profile ID</td><td><code>1</code></td></tr>
|
|
|
|
|
<tr><td><code>[PROFILE_NAME]</code></td><td>Profile title</td><td><code>default</code></td></tr>
|
|
|
|
|
<tr><td><code>[TYPE]</code></td><td>Backup type</td><td><code>full</code></td></tr>
|
|
|
|
|
<tr><td><code>[HOME]</code></td><td>Home directory of the PHP process owner. Detected from environment, POSIX, or JPATH_ROOT.</td><td><code>{$placeholders['[HOME]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[DEFAULT_DIR]</code></td><td>Default backup directory inside the Joomla web root. Protected by .htaccess but not recommended for production.</td><td><code>{$placeholders['[DEFAULT_DIR]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[HOST]</code></td><td>Server hostname from HTTP_HOST. Sanitized to alphanumeric, dots, and hyphens.</td><td><code>{$placeholders['[HOST]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[SITE_NAME]</code></td><td>Joomla site name from Global Configuration. Spaces become hyphens, special characters stripped.</td><td><code>{$placeholders['[SITE_NAME]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[DATE]</code></td><td>Current date in Ymd format (e.g. 20260623).</td><td><code>{$placeholders['[DATE]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[YEAR]</code></td><td>Four-digit year.</td><td><code>{$placeholders['[YEAR]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[MONTH]</code></td><td>Two-digit month (01-12).</td><td><code>{$placeholders['[MONTH]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[DAY]</code></td><td>Two-digit day (01-31).</td><td><code>{$placeholders['[DAY]']}</code></td></tr>
|
|
|
|
|
<tr><td><code>[PROFILE_ID]</code></td><td>Numeric ID of the backup profile being used.</td><td><code>1</code></td></tr>
|
|
|
|
|
<tr><td><code>[PROFILE_NAME]</code></td><td>Title of the backup profile, sanitized for filesystem use.</td><td><code>default</code></td></tr>
|
|
|
|
|
<tr><td><code>[TYPE]</code></td><td>Backup type: full, database, files, or differential.</td><td><code>full</code></td></tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
<h6>Recommended Paths</h6>
|
|
|
|
|
<ul class="list-unstyled">
|
|
|
|
|
<li><code>[HOME]/backups</code> — Outside web root (recommended)</li>
|
|
|
|
|
<li><code>[HOME]/backups/[HOST]</code> — Per-site subdirectory</li>
|
|
|
|
|
<li><code>[DEFAULT_DIR]</code> — Inside web root (protected by .htaccess)</li>
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
<h6 class="text-primary mt-3">Recommended Configurations</h6>
|
|
|
|
|
<table class="table table-sm">
|
|
|
|
|
<thead><tr><th>Use Case</th><th>Path</th><th>Notes</th></tr></thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr>
|
|
|
|
|
<td><strong>Single site, secure</strong></td>
|
|
|
|
|
<td><code>[HOME]/backups</code></td>
|
|
|
|
|
<td>Outside web root. Best for most sites.</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td><strong>Multiple sites on one server</strong></td>
|
|
|
|
|
<td><code>[HOME]/backups/[HOST]</code></td>
|
|
|
|
|
<td>Each site gets its own subdirectory.</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td><strong>Date-organized</strong></td>
|
|
|
|
|
<td><code>[HOME]/backups/[YEAR]/[MONTH]</code></td>
|
|
|
|
|
<td>Backups sorted by year and month.</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td><strong>Per-profile</strong></td>
|
|
|
|
|
<td><code>[HOME]/backups/[PROFILE_NAME]</code></td>
|
|
|
|
|
<td>Separate directory for each backup profile.</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td><strong>Shared hosting (default)</strong></td>
|
|
|
|
|
<td><code>[DEFAULT_DIR]</code></td>
|
|
|
|
|
<td>Inside web root, protected by .htaccess. Use only if you cannot write outside web root.</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
<div class="alert alert-info py-2 mt-3 mb-0">
|
|
|
|
|
<strong>Tip:</strong> The directory is created automatically if it doesn't exist. Placeholders are resolved fresh each time a backup runs, so date-based paths create new directories over time.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
|
|
@@ -188,6 +264,36 @@ class FolderPickerField extends FormField
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Help button — open modal with Bootstrap 5 or fallback */
|
|
|
|
|
var helpBtn = document.getElementById('{$id}_helpBtn');
|
|
|
|
|
var helpModal = document.getElementById('{$id}_helpModal');
|
|
|
|
|
if (helpBtn && helpModal) {
|
|
|
|
|
helpBtn.addEventListener('click', function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
|
|
|
|
var modal = bootstrap.Modal.getOrCreateInstance(helpModal);
|
|
|
|
|
modal.show();
|
|
|
|
|
} else {
|
|
|
|
|
helpModal.classList.add('show');
|
|
|
|
|
helpModal.style.display = 'block';
|
|
|
|
|
helpModal.setAttribute('aria-hidden', 'false');
|
|
|
|
|
document.body.classList.add('modal-open');
|
|
|
|
|
var backdrop = document.createElement('div');
|
|
|
|
|
backdrop.className = 'modal-backdrop fade show';
|
|
|
|
|
backdrop.id = '{$id}_backdrop';
|
|
|
|
|
document.body.appendChild(backdrop);
|
|
|
|
|
helpModal.querySelector('.btn-close, [data-bs-dismiss]').addEventListener('click', function() {
|
|
|
|
|
helpModal.classList.remove('show');
|
|
|
|
|
helpModal.style.display = 'none';
|
|
|
|
|
helpModal.setAttribute('aria-hidden', 'true');
|
|
|
|
|
document.body.classList.remove('modal-open');
|
|
|
|
|
var bd = document.getElementById('{$id}_backdrop');
|
|
|
|
|
if (bd) bd.remove();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var fieldId = '{$id}';
|
|
|
|
|
var btn = document.getElementById(fieldId + '_btn');
|
|
|
|
|
var browser = document.getElementById(fieldId + '_browser');
|
|
|
|
|