* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. * @license GNU General Public License version 3 or later; see LICENSE * * Differential file scanner — compares current filesystem against a * stored manifest from the last full backup. Only returns files that * are new or modified since the base backup. * * Manifest format (JSON): * {"path/to/file": {"size": 1234, "mtime": 1717350000}, ...} */ namespace Joomla\Component\MokoSuiteBackup\Administrator\Engine; defined('_JEXEC') or die; class DifferentialScanner { /** * Build a file manifest for the current state of the site. * * @param string[] $filePaths Array of relative file paths (from FileScanner) * @param string $rootDir Joomla root directory * * @return array */ public static function buildManifest(array $filePaths, string $rootDir): array { $manifest = []; foreach ($filePaths as $relativePath) { $fullPath = rtrim($rootDir, '/') . '/' . $relativePath; if (!is_file($fullPath)) { continue; } $manifest[$relativePath] = [ 'size' => (int) filesize($fullPath), 'mtime' => (int) filemtime($fullPath), ]; } return $manifest; } /** * Compare current files against a base manifest and return only changed/new files. * * @param array $currentFiles Array of relative file paths from FileScanner * @param array $baseManifest Manifest from the base full backup * @param string $rootDir Joomla root directory * * @return string[] Array of relative paths that are new or modified */ public static function getChangedFiles(array $currentFiles, array $baseManifest, string $rootDir): array { $changed = []; foreach ($currentFiles as $relativePath) { $fullPath = rtrim($rootDir, '/') . '/' . $relativePath; if (!is_file($fullPath)) { continue; } // New file — not in base manifest if (!isset($baseManifest[$relativePath])) { $changed[] = $relativePath; continue; } // Check if modified (size or mtime changed) $currentSize = (int) filesize($fullPath); $currentMtime = (int) filemtime($fullPath); $baseEntry = $baseManifest[$relativePath]; if ($currentSize !== $baseEntry['size'] || $currentMtime !== $baseEntry['mtime']) { $changed[] = $relativePath; } } return $changed; } }