"\n Require all denied\n\n\n Deny from all\n\n", 'index.php' => " '', ]; foreach ($files as $name => $contents) { if (!file_exists("$dir/$name")) { @file_put_contents("$dir/$name", $contents); } } } const FOREIGN = ['wpress', 'zip']; public static function new_archive_name(string $prefix = '', string $ext = self::EXT): string { $url = wp_parse_url(get_option('home')); $host = sanitize_title(($url['host'] ?? 'site') . ($url['path'] ?? '')); return sprintf('%s%s-%s-%s.%s', $prefix, $host ?: 'site', gmdate('Ymd-His'), strtolower(wp_generate_password(12, false)), $ext); } /** * Uploaded All-in-One (.wpress) or Duplicator (.zip) archive waiting for conversion. */ public static function foreign_path(string $file): ?string { $file = basename($file); if (!preg_match('/^uploaded-[A-Za-z0-9._-]+\.(' . implode('|', self::FOREIGN) . ')$/', $file)) { return null; } $path = self::dir() . '/' . $file; return is_file($path) ? $path : null; } /** * File index of a full backup (path, size, mtime separated by tabs, one per line), used as the base of differential backups. */ public static function index_path(string $archive): string { return substr($archive, 0, -strlen(self::EXT)) . 'idx'; } public static function read_index(string $archive): array { $index = []; $fh = @fopen(self::index_path($archive), 'rb'); if (!$fh) { return $index; } while (($line = fgets($fh)) !== false) { $tab = strpos($line, "\t"); if ($tab !== false) { $index[substr($line, 0, $tab)] = rtrim(substr($line, $tab + 1), "\r\n"); } } fclose($fh); return $index; } /** * Resolve a backup file name to a path inside the storage folder. */ public static function path(string $file): ?string { $file = basename($file); if (!preg_match('/^[A-Za-z0-9._-]+\.' . self::EXT . '$/', $file)) { return null; } $path = self::dir() . '/' . $file; return is_file($path) ? $path : null; } public static function meta_path(string $archive): string { return substr($archive, 0, -strlen(self::EXT)) . 'json'; } public static function read_meta(string $archive): array { $path = self::meta_path($archive); if (!is_file($path)) { return []; } $meta = json_decode((string) file_get_contents($path), true); return is_array($meta) ? $meta : []; } public static function write_meta(string $archive, array $meta): void { file_put_contents(self::meta_path($archive), wp_json_encode(array_merge(self::read_meta($archive), $meta))); } public static function backups(): array { $items = []; foreach (glob(self::dir() . '/*.' . self::EXT) ?: [] as $path) { $meta = self::read_meta($path); $items[] = [ 'file' => basename($path), 'size' => filesize($path), 'created' => (int) ($meta['created'] ?? filemtime($path)), 'origin' => $meta['origin'] ?? 'manual', 'label' => $meta['label'] ?? '', 'source' => $meta['source'] ?? '', 'components' => $meta['components'] ?? [], 'kind' => $meta['kind'] ?? 'full', 'base' => $meta['base'] ?? '', 'has_index' => is_file(self::index_path($path)), ]; } usort($items, function ($a, $b) { return $b['created'] <=> $a['created']; }); return $items; } public static function delete(string $file): bool { $path = self::path($file); if (!$path) { return false; } @unlink(self::meta_path($path)); @unlink(self::index_path($path)); return @unlink($path); } public static function free_space(): ?int { $free = function_exists('disk_free_space') ? @disk_free_space(self::dir()) : false; return $free === false ? null : (int) $free; } public static function rrmdir(string $dir): void { if (!is_dir($dir)) { return; } $items = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST); foreach ($items as $item) { $item->isDir() && !$item->isLink() ? @rmdir($item->getPathname()) : @unlink($item->getPathname()); } @rmdir($dir); } /** * Remove finished job folders and abandoned uploads older than a day. */ public static function cleanup(): void { $limit = time() - DAY_IN_SECONDS; foreach (glob(self::jobs_dir() . '/*', GLOB_ONLYDIR) ?: [] as $dir) { if (@filemtime($dir . '/state.json') < $limit) { self::rrmdir($dir); } } foreach (array_merge(glob(self::dir() . '/*.part') ?: [], glob(self::dir() . '/uploaded-*.{wpress,zip}', GLOB_BRACE) ?: []) as $leftover) { if (@filemtime($leftover) < $limit) { @unlink($leftover); } } } }