ensure_dir($dir); return $dir; } public function work_dir(): string { $dir = $this->base_dir() . '/tmp'; $this->ensure_dir($dir); return $dir; } public function job_dir(string $job_id): string { $dir = $this->work_dir() . '/' . preg_replace('/[^a-z0-9]/', '', strtolower($job_id)); $this->ensure_dir($dir); return $dir; } public function ensure_dir(string $dir): void { if (!is_dir($dir)) { wp_mkdir_p($dir); } $this->protect($dir); } public function protect(string $dir): void { $files = [ '.htaccess' => "# seljeBackup: deny all web access\n\n Require all denied\n\n\n Order allow,deny\n Deny from all\n\n", 'web.config' => "\n\n \n \n \n \n \n\n", 'index.php' => " '', ]; foreach ($files as $name => $content) { $path = $dir . '/' . $name; if (!file_exists($path) && is_dir($dir) && wp_is_writable($dir)) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents @file_put_contents($path, $content); } } } public function rrmdir(string $dir): void { if ($dir === '' || !is_dir($dir) || strpos(self::normalize($dir), $this->base_dir()) !== 0) { return; } $items = @scandir($dir); if (!is_array($items)) { return; } foreach ($items as $item) { if ($item === '.' || $item === '..') { continue; } $path = $dir . '/' . $item; if (is_dir($path) && !is_link($path)) { $this->rrmdir($path); } else { wp_delete_file($path); } } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir @rmdir($dir); } public function free_space(string $dir): ?float { if (!function_exists('disk_free_space')) { return null; } $free = @disk_free_space($dir); return $free === false ? null : (float)$free; } /** * Short, filesystem-safe name of this site used in backup file names, e.g. "example-com". */ public static function site_slug(): string { $host = (string)wp_parse_url(home_url(), PHP_URL_HOST); $path = (string)wp_parse_url(home_url(), PHP_URL_PATH); $slug = sanitize_title($host . $path); return $slug !== '' ? substr($slug, 0, 60) : 'wordpress'; } public static function normalize(string $path): string { $path = str_replace('\\', '/', $path); return (string)preg_replace('#/+#', '/', $path); } /** * Resolve a backup file name to its absolute path, refusing anything outside the backup folder. */ public function backup_file_path(string $name): ?string { if ($name === '' || $name !== basename($name) || strpos($name, '..') !== false) { return null; } $path = $this->base_dir() . '/' . $name; return is_file($path) ? $path : null; } }