config = $config; $this->progress = $progress; $this->outputDir = rtrim($outputDir, '/\\') . '/files/'; if (!is_dir($this->outputDir)) { wp_mkdir_p($this->outputDir); } } /** * Arşivlenecek dosya listesini hazırlar */ public function prepareFileList(): array { $upload_dir = wp_upload_dir(); $this->directories = [ 'uploads' => $upload_dir['basedir'], 'plugins' => defined('WP_PLUGIN_DIR') ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins', 'themes' => get_theme_root() ]; $this->fileList = []; $logFile = ALLDAMI_STORAGE_DIR . 'alldami-scan-debug.txt'; file_put_contents($logFile, "Scan Started at " . gmdate('Y-m-d H:i:s') . "\n"); foreach ($directories as $key => $path) { // Path'i normalize et $path = wp_normalize_path($path); file_put_contents($logFile, "Scanning Directory: $key => $path\n", FILE_APPEND); if (!is_dir($path)) { file_put_contents($logFile, "Directory NOT FOUND: $path\n", FILE_APPEND); continue; } try { // Basit iterator kullan (Prefix yok, Symlink takibi olmadan - Linkler Windows'ta riskli) $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY ); } catch (\Exception $e) { file_put_contents($logFile, "Iterator Error for $path: " . $e->getMessage() . "\n", FILE_APPEND); continue; } $count = 0; foreach ($iterator as $file) { // Dosya adında . veya .. varsa zaten SKIP_DOTS ile atlanıyor ama ekstra kontrol if ($file->isDir()) continue; $fullPath = wp_normalize_path($file->getPathname()); // EXCLUSIONS: // 1. wp-cloner dizinini tamamen atla (Recursive backup loop) // path zaten wp_normalize_path ile / 'a çevrildi, tek kontrol yeterli if (strpos($fullPath, '/uploads/alldami-site-migration/') !== false) { continue; } // 2. Gereksiz dev klasörlerini atla (node_modules, .git, .idea, vendor vs) // Bu klasörler plugin içinde olsa bile son kullanıcı için gereksiz olabilir ve çok yer kaplar // Ancak bazı pluginler vendor'a ihtiyaç duyar, o yüzden vendor'u hariç tutmak riskli olabilir. // SADECE node_modules ve .git kesin güvenlidir. if (strpos($fullPath, '/node_modules/') !== false || strpos($fullPath, '/.git/') !== false) { continue; } // Eğer dosya okunabiliyorsa ekle if ($file->isFile()) { // Base klasörlerin uzunluğu kadar kes, relative path oluştur $basePath = wp_normalize_path($this->directories[$key]); $relativePath = $key . '/' . ltrim(substr($fullPath, strlen($basePath)), '/'); $this->fileList[] = [ 'type' => $key, 'path' => $fullPath, 'relative' => $relativePath, 'size' => $file->getSize() ]; $count++; } } file_put_contents($logFile, "Found $count files in $key\n", FILE_APPEND); } // Debug: Toplam dosya sayısı $logFile = ALLDAMI_STORAGE_DIR . 'alldami-export-debug.txt'; file_put_contents($logFile, "Total files found: " . count($this->fileList) . "\n", FILE_APPEND); return $this->fileList; } /** * Bir chunk arşivler * * @param int $startIndex Başlangıç dosya indeksi * @return array|null Sonraki chunk bilgisi */ public function archiveChunk(int $startIndex): ?array { if (empty($this->fileList) || $startIndex >= count($this->fileList)) { return ['completed' => true, 'files_added' => 0]; } $zip = new \ZipArchive(); $chunkNumber = (int) floor($startIndex / $this->config->getFilesPerChunk()) + 1; $filename = sprintf('files_%03d.zip', $chunkNumber); $filepath = $this->outputDir . $filename; if ($zip->open($filepath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { throw new \Exception("Zip dosyası oluşturulamadı: {$filepath}"); } // CHDIR STRATEJİSİ: // Absolute path yerine, WP_CONTENT_DIR'e gidip relative path ile ekleyeceğiz. // Bu, Windows'un 260 karakter limitini aşmayı sağlar. $originalCwd = getcwd(); $base_content_dir = defined('WP_CONTENT_DIR') ? WP_CONTENT_DIR : untrailingslashit(ABSPATH) . '/wp-content'; @chdir($base_content_dir); $currentSize = 0; $filesAdded = 0; $maxSize = $this->config->getMaxZipSize(); $maxFiles = $this->config->getFilesPerChunk(); $startTime = microtime(true); $errorLog = ALLDAMI_STORAGE_DIR . 'alldami-zip-errors.txt'; for ($i = $startIndex; $i < count($this->fileList); $i++) { if (!$this->config->canContinue($startTime)) break; $file = $this->fileList[$i]; if ($currentSize + $file['size'] > $maxSize && $filesAdded > 0) break; // Dosya yolunu hazırla // file['path'] absolute path (clean). Örn: D:/.../wp-content/plugins/foo.php // Biz WP_CONTENT_DIR içindeyiz (D:/.../wp-content) // Local path: plugins/foo.php // Eğer dosya WP_CONTENT_DIR içinde değilse (symlink dışarıyı gösteriyorsa)? // Bu durumda absolute path kullanmak zorundayız. $content_dir_norm = wp_normalize_path(defined('WP_CONTENT_DIR') ? WP_CONTENT_DIR : untrailingslashit(ABSPATH) . '/wp-content'); $localPath = str_replace($content_dir_norm . '/', '', $file['path']); // Eğer replace çalışmadıysa (dosya dışarıdaysa), absolute path kalır. // Absolute path ise ve çok uzunsa yapacak bir şey yok, ama chdir şansımızı artırır. if (file_exists($file['path']) && is_readable($file['path'])) { // addFile'a local path veriyoruz, zip içine relative path (aynısı) koyuyoruz // Veya zip içine farklı relative koyabiliriz. // NOT: $file['relative'] zaten 'plugins/foo.php' formatında. // $localPath de 'plugins/foo.php' olmalı. if (!$zip->addFile($localPath, $file['relative'])) { // Hata durumunda logla file_put_contents($errorLog, "Failed to add: $localPath (Real: {$file['path']})\n", FILE_APPEND); } else { $currentSize += $file['size']; $filesAdded++; } } else { file_put_contents($errorLog, "Unreadable/Missing: {$file['path']}\n", FILE_APPEND); } if ($filesAdded >= $maxFiles) { $i++; break; } } // Zip kapatırken hata kontrolü $closeResult = @$zip->close(); @chdir($originalCwd); // Zip kapatma başarısız olduysa (disk dolu olabilir) if ($closeResult === false) { // Disk alanını kontrol et $freeSpace = @disk_free_space(dirname($filepath)); $freeSpaceMB = $freeSpace ? round($freeSpace / 1024 / 1024, 2) : 0; if ($freeSpaceMB < 100) { throw new \Exception( sprintf( 'Disk alanı yetersiz! Sadece %.2f MB boş alan var. ' . 'En az 500 MB boş alan gerekli. Lütfen gereksiz dosyaları temizleyin.', $freeSpaceMB ) ); } else { throw new \Exception( 'Zip dosyası kaydedilemedi. Disk yazma hatası oluştu. ' . 'Dosya: ' . $filename ); } } // Dosya boyutunu kontrol et if (!file_exists($filepath)) { throw new \Exception('Zip dosyası oluşturulamadı: ' . $filename); } $chunkSize = @filesize($filepath); if ($chunkSize === false) { $chunkSize = 0; } return [ 'completed' => ($i >= count($this->fileList)), 'next_index' => $i, 'files_added' => $filesAdded, 'chunk_size' => $chunkSize, 'chunk_file' => $filename ]; } /** * Büyük dosyayı ayrı chunk olarak arşivler */ public function archiveLargeFile(int $fileIndex): string { $file = $this->fileList[$fileIndex]; $zip = new \ZipArchive(); $filename = sprintf('large_%03d.zip', $fileIndex); $filepath = $this->outputDir . $filename; if ($zip->open($filepath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { throw new \Exception("Zip dosyası oluşturulamadı: {$filepath}"); } $zip->addFile($file['path'], $file['relative']); $zip->close(); return $filename; } public function getFileList(): array { return $this->fileList; } public function getFileCount(): int { return count($this->fileList); } public function getTotalSize(): int { return array_sum(array_column($this->fileList, 'size')); } /** * Resume pointer'dan dosya listesini geri yükler */ public function setFileList(array $fileList): void { $this->fileList = $fileList; } }