From 814d1b147c0a88c6cb7f42fbdc32a24f498e9231 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 7 Jun 2026 09:38:43 -0500 Subject: [PATCH 1/2] refactor: extract BackupDirectory utility to eliminate code duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create BackupDirectory utility class with centralized: - DEFAULT_RELATIVE constant and PLACEHOLDER constant - resolve() — path resolution with [DEFAULT_DIR] and relative path handling - hasPlaceholders() — check for unresolved placeholder tokens - isWebAccessible() — web-root boundary check - protect() — .htaccess and index.html creation with error logging - ensureReady() — mkdir + protect in one call - parseNewlineList() — newline-separated text parsing - logPathFromArchive() — derive .log path from archive path - Remove duplicated methods from BackupEngine, SteppedBackupEngine, ProfileTable, AjaxController, and DashboardModel - All consumers now use BackupDirectory static methods - Net reduction: ~180 lines of duplicated code eliminated --- .../src/Controller/AjaxController.php | 15 +- .../src/Engine/BackupEngine.php | 66 +------- .../src/Engine/PlaceholderResolver.php | 5 +- .../src/Engine/SteppedBackupEngine.php | 64 ++------ .../src/Field/FolderPickerField.php | 3 +- .../src/Model/DashboardModel.php | 23 +-- .../src/Table/ProfileTable.php | 41 +---- .../src/Utility/BackupDirectory.php | 153 ++++++++++++++++++ .../com_mokojoombackup/src/Utility/index.html | 1 + source/script.php | 18 ++- 10 files changed, 206 insertions(+), 183 deletions(-) create mode 100644 source/packages/com_mokojoombackup/src/Utility/BackupDirectory.php create mode 100644 source/packages/com_mokojoombackup/src/Utility/index.html diff --git a/source/packages/com_mokojoombackup/src/Controller/AjaxController.php b/source/packages/com_mokojoombackup/src/Controller/AjaxController.php index c924acd..6b3c3e6 100644 --- a/source/packages/com_mokojoombackup/src/Controller/AjaxController.php +++ b/source/packages/com_mokojoombackup/src/Controller/AjaxController.php @@ -18,6 +18,7 @@ defined('_JEXEC') or die; use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\Session\Session; use Joomla\Component\MokoJoomBackup\Administrator\Engine\SteppedBackupEngine; +use Joomla\Component\MokoJoomBackup\Administrator\Utility\BackupDirectory; class AjaxController extends BaseController { @@ -196,7 +197,7 @@ class AjaxController extends BaseController } // Try to load log from file alongside the archive - $logPath = preg_replace('/\.(zip|tar\.gz)$/i', '.log', $record->absolute_path); + $logPath = BackupDirectory::logPathFromArchive($record->absolute_path); $logContent = ''; if (is_file($logPath)) { @@ -239,17 +240,9 @@ class AjaxController extends BaseController return; } - // Resolve [DEFAULT_DIR] placeholder - $defaultDir = JPATH_ADMINISTRATOR . '/components/com_mokojoombackup/backups'; - $resolved = str_replace('[DEFAULT_DIR]', $defaultDir, $rawPath); + $resolved = BackupDirectory::resolve($rawPath); - // Resolve relative paths from JPATH_ROOT - if ($resolved !== '' && $resolved[0] !== '/' && !preg_match('#^[A-Za-z]:[/\\\\]#', $resolved)) { - $resolved = JPATH_ROOT . '/' . $resolved; - } - - // Skip check if unresolved placeholders remain - if (preg_match('/\[.+\]/', $resolved)) { + if (BackupDirectory::hasPlaceholders($resolved)) { $this->sendJson([ 'error' => false, 'exists' => null, diff --git a/source/packages/com_mokojoombackup/src/Engine/BackupEngine.php b/source/packages/com_mokojoombackup/src/Engine/BackupEngine.php index 84b3c49..d9863cf 100644 --- a/source/packages/com_mokojoombackup/src/Engine/BackupEngine.php +++ b/source/packages/com_mokojoombackup/src/Engine/BackupEngine.php @@ -13,6 +13,7 @@ namespace Joomla\Component\MokoJoomBackup\Administrator\Engine; defined('_JEXEC') or die; use Joomla\CMS\Factory; +use Joomla\Component\MokoJoomBackup\Administrator\Utility\BackupDirectory; use Joomla\Event\Event; class BackupEngine @@ -56,24 +57,20 @@ class BackupEngine } // Read settings directly from profile columns - $excludeDirs = $this->parseNewlineList($profile->exclude_dirs ?? ''); - $excludeFiles = $this->parseNewlineList($profile->exclude_files ?? ''); - $excludeTables = $this->parseNewlineList($profile->exclude_tables ?? ''); + $excludeDirs = BackupDirectory::parseNewlineList($profile->exclude_dirs ?? ''); + $excludeFiles = BackupDirectory::parseNewlineList($profile->exclude_files ?? ''); + $excludeTables = BackupDirectory::parseNewlineList($profile->exclude_tables ?? ''); // Resolve placeholders in directory and filename $resolver = new PlaceholderResolver($profile); - $configuredDir = $profile->backup_dir ?: '[DEFAULT_DIR]'; - $this->backupDir = $this->resolveBackupDir($resolver->resolve($configuredDir)); + $configuredDir = $profile->backup_dir ?: BackupDirectory::PLACEHOLDER; + $this->backupDir = BackupDirectory::resolve($resolver->resolve($configuredDir)); - if (!is_dir($this->backupDir)) { - if (!mkdir($this->backupDir, 0755, true)) { - return ['success' => false, 'message' => 'Cannot create backup directory: ' . $this->backupDir, 'record_id' => 0]; - } + if (!BackupDirectory::ensureReady($this->backupDir)) { + return ['success' => false, 'message' => 'Cannot create backup directory: ' . $this->backupDir, 'record_id' => 0]; } - $this->protectBackupDir($this->backupDir); - // Create backup record $now = date('Y-m-d H:i:s'); $tag = $resolver->getTag(); @@ -474,21 +471,6 @@ class BackupEngine $zip->close(); } - /** - * Parse a newline-separated text field into an array of trimmed, non-empty strings. - */ - private function parseNewlineList(string $text): array - { - if (empty($text)) { - return []; - } - - return array_values(array_filter( - array_map('trim', explode("\n", str_replace("\r", '', $text))), - fn($line) => $line !== '' - )); - } - /** * Dispatch the onMokoJoomBackupAfterRun event so plugins (actionlog, etc.) can react. */ @@ -512,38 +494,6 @@ class BackupEngine } } - /** - * Resolve a backup directory path. Absolute paths are used as-is, - * relative paths are resolved from JPATH_ROOT. - */ - private function resolveBackupDir(string $dir): string - { - if ($dir !== '' && ($dir[0] === '/' || preg_match('#^[A-Za-z]:[/\\\\]#', $dir))) { - return rtrim($dir, '/\\'); - } - - return JPATH_ROOT . '/' . $dir; - } - - private function protectBackupDir(string $dir): void - { - $htaccess = $dir . '/.htaccess'; - - if (!is_file($htaccess)) { - if (@file_put_contents($htaccess, "# Apache 2.4+\n\n Require all denied\n\n# Apache 2.2\n\n Order deny,allow\n Deny from all\n\n") === false) { - error_log('MokoJoomBackup: Could not create .htaccess in backup directory: ' . $dir); - } - } - - $index = $dir . '/index.html'; - - if (!is_file($index)) { - if (@file_put_contents($index, '') === false) { - error_log('MokoJoomBackup: Could not create index.html in backup directory: ' . $dir); - } - } - } - private function log(string $message): void { $this->log[] = '[' . date('H:i:s') . '] ' . $message; diff --git a/source/packages/com_mokojoombackup/src/Engine/PlaceholderResolver.php b/source/packages/com_mokojoombackup/src/Engine/PlaceholderResolver.php index a4afed0..094ed0f 100644 --- a/source/packages/com_mokojoombackup/src/Engine/PlaceholderResolver.php +++ b/source/packages/com_mokojoombackup/src/Engine/PlaceholderResolver.php @@ -16,6 +16,7 @@ namespace Joomla\Component\MokoJoomBackup\Administrator\Engine; defined('_JEXEC') or die; use Joomla\CMS\Factory; +use Joomla\Component\MokoJoomBackup\Administrator\Utility\BackupDirectory; class PlaceholderResolver { @@ -38,7 +39,7 @@ class PlaceholderResolver '[site_name]' => 'Joomla site name (sanitized)', '[type]' => 'Backup type (full, database, files, differential)', '[random]' => 'Random 6-character hex string', - '[DEFAULT_DIR]' => 'Default backup directory (administrator/components/com_mokojoombackup/backups)', + '[DEFAULT_DIR]' => 'Default backup directory', ]; private array $replacements; @@ -75,7 +76,7 @@ class PlaceholderResolver '[site_name]' => $this->sanitize($siteName ?: 'joomla'), '[type]' => $profile->backup_type ?? 'full', '[random]' => bin2hex(random_bytes(3)), - '[DEFAULT_DIR]' => JPATH_ADMINISTRATOR . '/components/com_mokojoombackup/backups', + '[DEFAULT_DIR]' => BackupDirectory::getDefaultAbsolute(), ]; } diff --git a/source/packages/com_mokojoombackup/src/Engine/SteppedBackupEngine.php b/source/packages/com_mokojoombackup/src/Engine/SteppedBackupEngine.php index fc5af70..152bd98 100644 --- a/source/packages/com_mokojoombackup/src/Engine/SteppedBackupEngine.php +++ b/source/packages/com_mokojoombackup/src/Engine/SteppedBackupEngine.php @@ -21,6 +21,7 @@ namespace Joomla\Component\MokoJoomBackup\Administrator\Engine; defined('_JEXEC') or die; use Joomla\CMS\Factory; +use Joomla\Component\MokoJoomBackup\Administrator\Utility\BackupDirectory; class SteppedBackupEngine { @@ -52,26 +53,22 @@ class SteppedBackupEngine $session->backupType = $profile->backup_type; // Parse profile settings - $session->excludeDirs = $this->parseNewlineList($profile->exclude_dirs ?? ''); - $session->excludeFiles = $this->parseNewlineList($profile->exclude_files ?? ''); - $session->excludeTables = $this->parseNewlineList($profile->exclude_tables ?? ''); - $session->backupDir = $profile->backup_dir ?: '[DEFAULT_DIR]'; + $session->excludeDirs = BackupDirectory::parseNewlineList($profile->exclude_dirs ?? ''); + $session->excludeFiles = BackupDirectory::parseNewlineList($profile->exclude_files ?? ''); + $session->excludeTables = BackupDirectory::parseNewlineList($profile->exclude_tables ?? ''); + $session->backupDir = $profile->backup_dir ?: BackupDirectory::PLACEHOLDER; $session->remoteStorage = $profile->remote_storage ?? 'none'; $session->includeMokoRestore = (bool) ($profile->include_mokorestore ?? false); $session->remoteKeepLocal = (bool) ($profile->remote_keep_local ?? true); // Resolve placeholders in directory and filename $resolver = new PlaceholderResolver($profile); - $backupDir = $this->resolveBackupDir($resolver->resolve($session->backupDir)); + $backupDir = BackupDirectory::resolve($resolver->resolve($session->backupDir)); - if (!is_dir($backupDir)) { - if (!mkdir($backupDir, 0755, true)) { - return ['error' => true, 'message' => 'Cannot create backup directory: ' . $backupDir]; - } + if (!BackupDirectory::ensureReady($backupDir)) { + return ['error' => true, 'message' => 'Cannot create backup directory: ' . $backupDir]; } - $this->protectBackupDir($backupDir); - $now = date('Y-m-d H:i:s'); $tag = $resolver->getTag(); $nameFormat = $profile->archive_name_format ?? '[host]_[datetime]_profile[profile_id]'; @@ -422,7 +419,7 @@ class SteppedBackupEngine $logContent = implode("\n", $session->log); // Write log file alongside the archive - $logPath = preg_replace('/\.(zip|tar\.gz)$/i', '.log', $session->archivePath); + $logPath = BackupDirectory::logPathFromArchive($session->archivePath); if (@file_put_contents($logPath, $logContent) === false) { error_log('MokoJoomBackup: Could not write log file: ' . $logPath); } @@ -554,47 +551,4 @@ class SteppedBackupEngine return $tables; } - /** - * Resolve a backup directory path. Absolute paths are used as-is, - * relative paths are resolved from JPATH_ROOT. - */ - private function resolveBackupDir(string $dir): string - { - if ($dir !== '' && ($dir[0] === '/' || preg_match('#^[A-Za-z]:[/\\\\]#', $dir))) { - return rtrim($dir, '/\\'); - } - - return JPATH_ROOT . '/' . $dir; - } - - private function protectBackupDir(string $dir): void - { - $htaccess = $dir . '/.htaccess'; - - if (!is_file($htaccess)) { - if (@file_put_contents($htaccess, "# Apache 2.4+\n\n Require all denied\n\n# Apache 2.2\n\n Order deny,allow\n Deny from all\n\n") === false) { - error_log('MokoJoomBackup: Could not create .htaccess in backup directory: ' . $dir); - } - } - - $index = $dir . '/index.html'; - - if (!is_file($index)) { - if (@file_put_contents($index, '') === false) { - error_log('MokoJoomBackup: Could not create index.html in backup directory: ' . $dir); - } - } - } - - private function parseNewlineList(string $text): array - { - if (empty($text)) { - return []; - } - - return array_values(array_filter( - array_map('trim', explode("\n", str_replace("\r", '', $text))), - fn($line) => $line !== '' - )); - } } diff --git a/source/packages/com_mokojoombackup/src/Field/FolderPickerField.php b/source/packages/com_mokojoombackup/src/Field/FolderPickerField.php index 17bcaee..9ba5187 100644 --- a/source/packages/com_mokojoombackup/src/Field/FolderPickerField.php +++ b/source/packages/com_mokojoombackup/src/Field/FolderPickerField.php @@ -15,6 +15,7 @@ defined('_JEXEC') or die; use Joomla\CMS\Factory; use Joomla\CMS\Form\FormField; use Joomla\CMS\Language\Text; +use Joomla\Component\MokoJoomBackup\Administrator\Utility\BackupDirectory; class FolderPickerField extends FormField { @@ -49,7 +50,7 @@ class FolderPickerField extends FormField $sanitizedSiteName = preg_replace('/[^a-zA-Z0-9._-]/', '', str_replace(' ', '-', trim($siteName))); $placeholders = [ - '[DEFAULT_DIR]' => JPATH_ADMINISTRATOR . '/components/com_mokojoombackup/backups', + '[DEFAULT_DIR]' => BackupDirectory::getDefaultAbsolute(), '[host]' => $hostname, '[site_name]' => $sanitizedSiteName ?: 'joomla', '[profile_id]' => '1', diff --git a/source/packages/com_mokojoombackup/src/Model/DashboardModel.php b/source/packages/com_mokojoombackup/src/Model/DashboardModel.php index 4435e54..778f12b 100644 --- a/source/packages/com_mokojoombackup/src/Model/DashboardModel.php +++ b/source/packages/com_mokojoombackup/src/Model/DashboardModel.php @@ -14,6 +14,7 @@ defined('_JEXEC') or die; use Joomla\CMS\Factory; use Joomla\CMS\MVC\Model\BaseDatabaseModel; +use Joomla\Component\MokoJoomBackup\Administrator\Utility\BackupDirectory; class DashboardModel extends BaseDatabaseModel { @@ -122,11 +123,9 @@ class DashboardModel extends BaseDatabaseModel 'detail' => $aesSupport ? 'Available' : 'Requires libzip 1.2.0+', ]; - // Backup directory writable — check the default path - $defaultDir = JPATH_ADMINISTRATOR . '/components/com_mokojoombackup/backups'; - $backupDir = $defaultDir; + // Backup directory writable — check the first published profile's dir + $backupDir = BackupDirectory::getDefaultAbsolute(); - // If profiles use a custom directory, check that instead $db2 = $this->getDatabase(); $qDir = $db2->getQuery(true) ->select($db2->quoteName('backup_dir')) @@ -138,16 +137,10 @@ class DashboardModel extends BaseDatabaseModel $profileDir = $db2->loadResult(); if ($profileDir) { - // Absolute paths used as-is, relative resolved from JPATH_ROOT - if ($profileDir[0] === '/' || preg_match('#^[A-Za-z]:[/\\\\]#', $profileDir)) { - $backupDir = rtrim($profileDir, '/\\'); - } else { - $backupDir = JPATH_ROOT . '/' . $profileDir; - } + $backupDir = BackupDirectory::resolve($profileDir); } - // Skip filesystem check if path contains placeholders (resolved at backup time) - if (preg_match('/\[.+\]/', $backupDir)) { + if (BackupDirectory::hasPlaceholders($backupDir)) { $checks[] = (object) [ 'label' => 'Backup Directory', 'status' => true, @@ -182,14 +175,12 @@ class DashboardModel extends BaseDatabaseModel public function isUsingDefaultBackupDir(): bool { $db = $this->getDatabase(); - $default = 'administrator/components/com_mokojoombackup/backups'; - $query = $db->getQuery(true) ->select('COUNT(*)') ->from($db->quoteName('#__mokojoombackup_profiles')) ->where($db->quoteName('published') . ' = 1') - ->where('(' . $db->quoteName('backup_dir') . ' = ' . $db->quote($default) - . ' OR ' . $db->quoteName('backup_dir') . ' = ' . $db->quote('[DEFAULT_DIR]') + ->where('(' . $db->quoteName('backup_dir') . ' = ' . $db->quote(BackupDirectory::DEFAULT_RELATIVE) + . ' OR ' . $db->quoteName('backup_dir') . ' = ' . $db->quote(BackupDirectory::PLACEHOLDER) . ' OR ' . $db->quoteName('backup_dir') . ' = ' . $db->quote('') . ' OR ' . $db->quoteName('backup_dir') . ' IS NULL)'); $db->setQuery($query); diff --git a/source/packages/com_mokojoombackup/src/Table/ProfileTable.php b/source/packages/com_mokojoombackup/src/Table/ProfileTable.php index 78892a7..980bae5 100644 --- a/source/packages/com_mokojoombackup/src/Table/ProfileTable.php +++ b/source/packages/com_mokojoombackup/src/Table/ProfileTable.php @@ -13,6 +13,7 @@ namespace Joomla\Component\MokoJoomBackup\Administrator\Table; defined('_JEXEC') or die; use Joomla\CMS\Table\Table; +use Joomla\Component\MokoJoomBackup\Administrator\Utility\BackupDirectory; use Joomla\Database\DatabaseDriver; class ProfileTable extends Table @@ -35,49 +36,17 @@ class ProfileTable extends Table private function protectWebAccessibleDir(string $dir): void { - // Resolve [DEFAULT_DIR] placeholder - $defaultDir = JPATH_ADMINISTRATOR . '/components/com_mokojoombackup/backups'; - $resolved = str_replace('[DEFAULT_DIR]', $defaultDir, $dir); + $resolved = BackupDirectory::resolve($dir); - // Resolve relative paths from JPATH_ROOT - if ($resolved !== '' && $resolved[0] !== '/' && !preg_match('#^[A-Za-z]:[/\\\\]#', $resolved)) { - $resolved = JPATH_ROOT . '/' . $resolved; - } - - // Skip if unresolved placeholders remain - if (preg_match('/\[.+\]/', $resolved)) { + if (BackupDirectory::hasPlaceholders($resolved)) { return; } - // Only protect directories under the web root - $jRoot = realpath(JPATH_ROOT) ?: JPATH_ROOT; - $realDir = realpath($resolved) ?: $resolved; - - if (strpos($realDir, $jRoot) !== 0) { + if (!BackupDirectory::isWebAccessible($resolved)) { return; } - if (!is_dir($resolved)) { - @mkdir($resolved, 0755, true); - } - - if (is_dir($resolved)) { - $htaccess = $resolved . '/.htaccess'; - - if (!is_file($htaccess)) { - if (@file_put_contents($htaccess, "# Apache 2.4+\n\n Require all denied\n\n# Apache 2.2\n\n Order deny,allow\n Deny from all\n\n") === false) { - error_log('MokoJoomBackup: Could not create .htaccess in: ' . $resolved); - } - } - - $index = $resolved . '/index.html'; - - if (!is_file($index)) { - if (@file_put_contents($index, '') === false) { - error_log('MokoJoomBackup: Could not create index.html in: ' . $resolved); - } - } - } + BackupDirectory::ensureReady($resolved); } public function check(): bool diff --git a/source/packages/com_mokojoombackup/src/Utility/BackupDirectory.php b/source/packages/com_mokojoombackup/src/Utility/BackupDirectory.php new file mode 100644 index 0000000..9048f19 --- /dev/null +++ b/source/packages/com_mokojoombackup/src/Utility/BackupDirectory.php @@ -0,0 +1,153 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + */ + +namespace Joomla\Component\MokoJoomBackup\Administrator\Utility; + +defined('_JEXEC') or die; + +class BackupDirectory +{ + public const DEFAULT_RELATIVE = 'administrator/components/com_mokojoombackup/backups'; + + public const PLACEHOLDER = '[DEFAULT_DIR]'; + + private const HTACCESS_CONTENT = <<<'HTACCESS' +# Apache 2.4+ + + Require all denied + +# Apache 2.2 + + Order deny,allow + Deny from all + +HTACCESS; + + private const INDEX_CONTENT = ''; + + /** + * Get the absolute default backup directory path. + */ + public static function getDefaultAbsolute(): string + { + return JPATH_ADMINISTRATOR . '/components/com_mokojoombackup/backups'; + } + + /** + * Resolve a backup directory path. Replaces [DEFAULT_DIR] placeholder, + * then resolves relative paths from JPATH_ROOT. + * + * @param string $dir Raw directory value from profile + * + * @return string Absolute path (may still contain other placeholders) + */ + public static function resolve(string $dir): string + { + if ($dir === '' || $dir === self::PLACEHOLDER) { + $dir = self::getDefaultAbsolute(); + } else { + $dir = str_replace(self::PLACEHOLDER, self::getDefaultAbsolute(), $dir); + } + + if ($dir !== '' && ($dir[0] === '/' || preg_match('#^[A-Za-z]:[/\\\\]#', $dir))) { + return rtrim($dir, '/\\'); + } + + return JPATH_ROOT . '/' . $dir; + } + + /** + * Check whether a resolved path still contains unresolved placeholders. + */ + public static function hasPlaceholders(string $path): bool + { + return (bool) preg_match('/\[.+\]/', $path); + } + + /** + * Check whether a resolved absolute path is inside the web root. + */ + public static function isWebAccessible(string $absolutePath): bool + { + $jRoot = realpath(JPATH_ROOT) ?: JPATH_ROOT; + $realDir = realpath($absolutePath) ?: $absolutePath; + + return strpos($realDir, $jRoot) === 0; + } + + /** + * Create .htaccess and index.html protection files in a directory. + * Only creates files if they don't already exist. + */ + public static function protect(string $dir): void + { + if (!is_dir($dir)) { + return; + } + + $htaccess = $dir . '/.htaccess'; + + if (!is_file($htaccess)) { + if (@file_put_contents($htaccess, self::HTACCESS_CONTENT . "\n") === false) { + error_log('MokoJoomBackup: Could not create .htaccess in: ' . $dir); + } + } + + $index = $dir . '/index.html'; + + if (!is_file($index)) { + if (@file_put_contents($index, self::INDEX_CONTENT) === false) { + error_log('MokoJoomBackup: Could not create index.html in: ' . $dir); + } + } + } + + /** + * Ensure the backup directory exists, create it if needed, + * and apply web protection if it's inside the web root. + * + * @return bool True if directory exists and is usable + */ + public static function ensureReady(string $dir): bool + { + if (!is_dir($dir)) { + if (!@mkdir($dir, 0755, true)) { + return false; + } + } + + self::protect($dir); + + return true; + } + + /** + * Parse a newline-separated text field into an array of trimmed, non-empty strings. + */ + public static function parseNewlineList(string $text): array + { + if (empty($text)) { + return []; + } + + return array_values(array_filter( + array_map('trim', explode("\n", str_replace("\r", '', $text))), + fn($line) => $line !== '' + )); + } + + /** + * Derive the log file path from an archive path. + */ + public static function logPathFromArchive(string $archivePath): string + { + return preg_replace('/\.(zip|tar\.gz)$/i', '.log', $archivePath); + } +} diff --git a/source/packages/com_mokojoombackup/src/Utility/index.html b/source/packages/com_mokojoombackup/src/Utility/index.html new file mode 100644 index 0000000..2efb97f --- /dev/null +++ b/source/packages/com_mokojoombackup/src/Utility/index.html @@ -0,0 +1 @@ + diff --git a/source/script.php b/source/script.php index a5b776c..0ceb68d 100644 --- a/source/script.php +++ b/source/script.php @@ -191,15 +191,25 @@ class Pkg_MokoJoomBackupInstallerScript $db->setQuery($query); $db->execute(); - // Create default backup directory + // Create and protect default backup directory $backupDir = JPATH_ADMINISTRATOR . '/components/com_mokojoombackup/backups'; if (!is_dir($backupDir)) { mkdir($backupDir, 0755, true); + } - // Protect backup directory with .htaccess - file_put_contents($backupDir . '/.htaccess', "# Apache 2.4+\n\n Require all denied\n\n# Apache 2.2\n\n Order deny,allow\n Deny from all\n\n"); - file_put_contents($backupDir . '/index.html', ''); + if (is_dir($backupDir)) { + $htaccess = $backupDir . '/.htaccess'; + + if (!is_file($htaccess)) { + file_put_contents($htaccess, "# Apache 2.4+\n\n Require all denied\n\n# Apache 2.2\n\n Order deny,allow\n Deny from all\n\n"); + } + + $index = $backupDir . '/index.html'; + + if (!is_file($index)) { + file_put_contents($index, ''); + } } } -- 2.52.0 From 139423cbe9b52a676a5981f8fb03f3eff54430cc Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Sun, 7 Jun 2026 14:41:18 +0000 Subject: [PATCH 2/2] chore(version): pre-release bump to 01.06.01-dev [skip ci] --- .mokogitea/manifest.xml | 2 +- .mokogitea/workflows/issue-branch.yml | 2 +- README.md | 2 +- source/packages/com_mokojoombackup/mokojoombackup.xml | 2 +- source/packages/plg_actionlog_mokojoombackup/mokojoombackup.xml | 2 +- source/packages/plg_console_mokojoombackup/mokojoombackup.xml | 2 +- source/packages/plg_content_mokojoombackup/mokojoombackup.xml | 2 +- source/packages/plg_quickicon_mokojoombackup/mokojoombackup.xml | 2 +- source/packages/plg_system_mokojoombackup/mokojoombackup.xml | 2 +- source/packages/plg_task_mokojoombackup/mokojoombackup.xml | 2 +- .../packages/plg_webservices_mokojoombackup/mokojoombackup.xml | 2 +- source/pkg_mokojoombackup.xml | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index b278f8b..cf696d8 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -5,7 +5,7 @@ Package - MokoJoomBackup MokoConsulting Full-site backup and restore for Joomla — database, files, and configuration - 01.06.00-dev + 01.06.01-dev GNU General Public License v3 diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 621b899..2c0bfe5 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokoplatform.Automation -# VERSION: 01.06.00 +# VERSION: 01.06.01 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/README.md b/README.md index 8044fea..183aadd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MokoJoomBackup - + Full-site backup and restore for Joomla — database, files, and configuration. diff --git a/source/packages/com_mokojoombackup/mokojoombackup.xml b/source/packages/com_mokojoombackup/mokojoombackup.xml index 3efdff6..4e329e5 100644 --- a/source/packages/com_mokojoombackup/mokojoombackup.xml +++ b/source/packages/com_mokojoombackup/mokojoombackup.xml @@ -8,7 +8,7 @@ --> com_mokojoombackup - 01.06.00 + 01.06.01-dev 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokojoombackup/mokojoombackup.xml b/source/packages/plg_actionlog_mokojoombackup/mokojoombackup.xml index 4d5a2fc..78ba61b 100644 --- a/source/packages/plg_actionlog_mokojoombackup/mokojoombackup.xml +++ b/source/packages/plg_actionlog_mokojoombackup/mokojoombackup.xml @@ -8,7 +8,7 @@ --> plg_actionlog_mokojoombackup - 01.06.00 + 01.06.01-dev 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokojoombackup/mokojoombackup.xml b/source/packages/plg_console_mokojoombackup/mokojoombackup.xml index ffe491f..db4a597 100644 --- a/source/packages/plg_console_mokojoombackup/mokojoombackup.xml +++ b/source/packages/plg_console_mokojoombackup/mokojoombackup.xml @@ -8,7 +8,7 @@ --> plg_console_mokojoombackup - 01.06.00 + 01.06.01-dev 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokojoombackup/mokojoombackup.xml b/source/packages/plg_content_mokojoombackup/mokojoombackup.xml index 78a1df3..9a2d0c3 100644 --- a/source/packages/plg_content_mokojoombackup/mokojoombackup.xml +++ b/source/packages/plg_content_mokojoombackup/mokojoombackup.xml @@ -8,7 +8,7 @@ --> plg_content_mokojoombackup - 01.06.00 + 01.06.01-dev 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokojoombackup/mokojoombackup.xml b/source/packages/plg_quickicon_mokojoombackup/mokojoombackup.xml index 1f2c4bd..a2e801e 100644 --- a/source/packages/plg_quickicon_mokojoombackup/mokojoombackup.xml +++ b/source/packages/plg_quickicon_mokojoombackup/mokojoombackup.xml @@ -1,7 +1,7 @@ plg_quickicon_mokojoombackup - 01.06.00 + 01.06.01-dev 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokojoombackup/mokojoombackup.xml b/source/packages/plg_system_mokojoombackup/mokojoombackup.xml index 90d6794..ca26b17 100644 --- a/source/packages/plg_system_mokojoombackup/mokojoombackup.xml +++ b/source/packages/plg_system_mokojoombackup/mokojoombackup.xml @@ -8,7 +8,7 @@ --> plg_system_mokojoombackup - 01.06.00 + 01.06.01-dev 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokojoombackup/mokojoombackup.xml b/source/packages/plg_task_mokojoombackup/mokojoombackup.xml index 0963993..7d91a30 100644 --- a/source/packages/plg_task_mokojoombackup/mokojoombackup.xml +++ b/source/packages/plg_task_mokojoombackup/mokojoombackup.xml @@ -8,7 +8,7 @@ --> plg_task_mokojoombackup - 01.06.00 + 01.06.01-dev 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokojoombackup/mokojoombackup.xml b/source/packages/plg_webservices_mokojoombackup/mokojoombackup.xml index 1f9a674..76c500d 100644 --- a/source/packages/plg_webservices_mokojoombackup/mokojoombackup.xml +++ b/source/packages/plg_webservices_mokojoombackup/mokojoombackup.xml @@ -8,7 +8,7 @@ --> plg_webservices_mokojoombackup - 01.06.00 + 01.06.01-dev 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokojoombackup.xml b/source/pkg_mokojoombackup.xml index 6dd458b..f4a39f7 100644 --- a/source/pkg_mokojoombackup.xml +++ b/source/pkg_mokojoombackup.xml @@ -8,7 +8,7 @@ Package - MokoJoomBackup mokojoombackup - 01.06.00 + 01.06.01-dev 2026-06-02 Moko Consulting hello@mokoconsulting.tech -- 2.52.0