Files
MokoSuiteBackup/source/packages/com_mokobackup/src/Engine/FileRestorer.php
T
Jonathan Miller a13f7ca6a6
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
chore: rename src/ to source/ per MokoStandards convention
Update all references in Makefile, manifest.xml, .gitignore, and CI
workflows (ci-joomla, pr-check, repo-health) to use source/ as the
primary directory with src/ as a fallback for compatibility.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 08:08:33 -05:00

124 lines
2.8 KiB
PHP

<?php
/**
* @package MokoJoomBackup
* @subpackage com_mokobackup
* @author Moko Consulting <hello@mokoconsulting.tech>
* @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);
}
}