feat: change default backup dir to ../backups (outside web root)

- Default backup_dir is now ../backups (relative to JPATH_ROOT),
  which resolves outside public_html on most hosting setups
- Added BackupDirectory::normalizePath() to resolve ../ segments
  without requiring the path to exist on disk
- Added BackupDirectory::portablize() to auto-detect absolute paths
  and replace them with portable placeholders ([HOME], ../backups)
- ProfileTable::check() auto-normalizes backup_dir on save
- Install postflight auto-migrates old in-webroot defaults to ../backups
- Dashboard warning now checks resolved path instead of string matching
- .htaccess protection only applied when directory is inside web root
This commit is contained in:
Jonathan Miller
2026-06-11 12:39:59 -05:00
parent 8d78cae60b
commit 49f3d9fdcf
7 changed files with 132 additions and 46 deletions
+22 -29
View File
@@ -191,25 +191,11 @@ class Pkg_MokoSuiteBackupInstallerScript
$db->setQuery($query);
$db->execute();
// Create and protect default backup directory
$backupDir = JPATH_ADMINISTRATOR . '/components/com_mokosuitebackup/backups';
// Create default backup directory (outside web root)
$backupDir = JPATH_ROOT . '/../backups';
if (!is_dir($backupDir)) {
mkdir($backupDir, 0755, true);
}
if (is_dir($backupDir)) {
$htaccess = $backupDir . '/.htaccess';
if (!is_file($htaccess)) {
file_put_contents($htaccess, "# Apache 2.4+\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n# Apache 2.2\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\n");
}
$index = $backupDir . '/index.html';
if (!is_file($index)) {
file_put_contents($index, '<!DOCTYPE html><title></title>');
}
@mkdir($backupDir, 0755, true);
}
// Create default scheduled task — every 30 days, profile 1
@@ -247,26 +233,33 @@ class Pkg_MokoSuiteBackupInstallerScript
{
try {
$db = Factory::getDbo();
// Check for profiles using the old in-webroot default
$oldDefaults = [
'administrator/components/com_mokosuitebackup/backups',
'administrator/components/com_mokojoombackup/backups',
'[DEFAULT_DIR]',
];
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokosuitebackup_profiles'))
->where($db->quoteName('published') . ' = 1')
->where('(' . $db->quoteName('backup_dir') . ' = ' . $db->quote('administrator/components/com_mokosuitebackup/backups')
. ' OR ' . $db->quoteName('backup_dir') . ' = ' . $db->quote('[DEFAULT_DIR]')
. ' OR ' . $db->quoteName('backup_dir') . ' = ' . $db->quote('')
->where('(' . $db->quoteName('backup_dir') . ' IN ('
. implode(',', array_map([$db, 'quote'], $oldDefaults))
. ') OR ' . $db->quoteName('backup_dir') . ' = ' . $db->quote('')
. ' OR ' . $db->quoteName('backup_dir') . ' IS NULL)');
$db->setQuery($query);
if ((int) $db->loadResult() > 0) {
$profileUrl = Route::_('index.php?option=com_mokosuitebackup&view=profiles');
Factory::getApplication()->enqueueMessage(
'<strong>Backup Directory Warning</strong> — '
. 'One or more profiles store backups in the default directory inside the web root. '
. 'For better security, configure a backup directory outside the web root. '
. '<a href="' . $profileUrl . '" class="btn btn-sm btn-warning ms-2">Edit Profiles</a>',
'warning'
);
// Auto-migrate old defaults to the new ../backups convention
$update = $db->getQuery(true)
->update($db->quoteName('#__mokosuitebackup_profiles'))
->set($db->quoteName('backup_dir') . ' = ' . $db->quote('../backups'))
->where('(' . $db->quoteName('backup_dir') . ' IN ('
. implode(',', array_map([$db, 'quote'], $oldDefaults))
. ') OR ' . $db->quoteName('backup_dir') . ' = ' . $db->quote('')
. ' OR ' . $db->quoteName('backup_dir') . ' IS NULL)');
$db->setQuery($update);
$db->execute();
}
} catch (\Throwable $e) {
error_log('MokoSuiteBackup: warnDefaultBackupDir() failed: ' . $e->getMessage());