* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @license GNU General Public License version 3 or later; see LICENSE * * Restores files from a staging directory to the Joomla root. * Skips database.sql and sensitive files that should not be overwritten. */ namespace Joomla\Component\MokoBackup\Administrator\Engine; defined('_JEXEC') or die; class FileRestorer { private string $sourceDir; private string $targetDir; /** * Files that should never be overwritten during restore. * configuration.php is handled separately by the RestoreEngine. */ private const SKIP_FILES = [ 'configuration.php', '.htaccess', 'web.config', ]; /** * Files that are backup artifacts, not part of the site. */ private const EXCLUDE_FILES = [ 'database.sql', ]; public function __construct(string $sourceDir, string $targetDir) { $this->sourceDir = rtrim($sourceDir, '/\\'); $this->targetDir = rtrim($targetDir, '/\\'); } /** * Copy files from staging to target, preserving directory structure. * * @return int Number of files restored */ public function restore(): int { $count = 0; $this->restoreDirectory('', $count); return $count; } private function restoreDirectory(string $relativePath, int &$count): void { $sourcePath = $this->sourceDir . ($relativePath ? '/' . $relativePath : ''); if (!is_dir($sourcePath)) { return; } $handle = opendir($sourcePath); if ($handle === false) { return; } while (($entry = readdir($handle)) !== false) { if ($entry === '.' || $entry === '..') { continue; } $entryRelative = $relativePath ? $relativePath . '/' . $entry : $entry; $entrySource = $sourcePath . '/' . $entry; $entryTarget = $this->targetDir . '/' . $entryRelative; if (is_dir($entrySource)) { // Create target directory if it doesn't exist if (!is_dir($entryTarget)) { mkdir($entryTarget, 0755, true); } $this->restoreDirectory($entryRelative, $count); } elseif (is_file($entrySource)) { // Skip excluded files if (in_array($entry, self::EXCLUDE_FILES, true)) { continue; } // Skip protected files (only at root level) if ($relativePath === '' && in_array($entry, self::SKIP_FILES, true)) { continue; } // Ensure parent directory exists $parentDir = dirname($entryTarget); if (!is_dir($parentDir)) { mkdir($parentDir, 0755, true); } // Copy file, preserving permissions if (copy($entrySource, $entryTarget)) { // Try to match original permissions $perms = fileperms($entrySource); if ($perms !== false) { @chmod($entryTarget, $perms); } $count++; } } } closedir($handle); } }