'latest', 'region' => 'ru-1', 'endpoint' => 'https://s3.twcstorage.ru', 'use_path_style_endpoint' => true, 'credentials' => [ 'key' => '2OMAK5ZNM900TAXM16J7', 'secret' => 'f4ADllb5VZBAt2HdsyB8WcwVEU7U74MwFCa1DARG', ], ]); $bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c'; $logFile = __DIR__ . '/logs/migration_final_' . date('Y-m-d_H-i-s') . '.log'; if (!is_dir(__DIR__ . '/logs')) { mkdir(__DIR__ . '/logs', 0755, true); } function writeLog($message) { global $logFile; $logMessage = "[" . date('Y-m-d H:i:s') . "] $message\n"; file_put_contents($logFile, $logMessage, FILE_APPEND); echo $message . "\n"; } function sanitizeFileName($name) { $name = str_replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|'], '_', $name); $name = preg_replace('/\s+/', ' ', $name); return trim($name); } writeLog("🚀 === МИГРАЦИЯ ПРОЕКТА $projectId ==="); if ($dryRun) writeLog("⚠️ DRY-RUN MODE"); // Получаем документы $sql = "SELECT n.* FROM vtiger_notes n INNER JOIN vtiger_senotesrel r ON r.notesid = n.notesid WHERE r.crmid = ? AND n.filelocationtype = 'E' ORDER BY n.notesid"; $result = $adb->pquery($sql, [$projectId]); $count = $adb->num_rows($result); writeLog("📋 Документов: $count"); $newFolderPath = "crm2/CRM_Active_Files/Documents/проекта_{$projectId}"; $stats = ['total' => $count, 'success' => 0, 'errors' => 0, 'skipped' => 0]; $usedNames = []; for ($i = 0; $i < $count; $i++) { $doc = $adb->fetchByAssoc($result); $docId = $doc['notesid']; $title = sanitizeFileName($doc['title']); $oldUrl = $doc['filename']; writeLog("\n📄 [$docId] {$doc['title']}"); // Извлекаем S3 путь из URL if (strpos($oldUrl, "https://s3.twcstorage.ru/$bucket/") === 0) { $oldS3PathEncoded = str_replace("https://s3.twcstorage.ru/$bucket/", '', $oldUrl); $oldS3Path = urldecode($oldS3PathEncoded); } else { writeLog(" ⚠️ Нестандартный формат URL"); $stats['skipped']++; continue; } // Формируем новое имя $extension = pathinfo(basename($oldS3Path), PATHINFO_EXTENSION); $baseNewName = $title ? "{$title}_{$docId}" : "document_{$docId}"; $newFileName = $baseNewName . ($extension ? ".$extension" : ''); // Проверка дубликатов $counter = 1; $finalNewName = $newFileName; while (isset($usedNames[$finalNewName])) { $finalNewName = $baseNewName . "_{$counter}" . ($extension ? ".$extension" : ''); $counter++; } $usedNames[$finalNewName] = true; $newS3Path = "$newFolderPath/$finalNewName"; // Проверяем уже мигрирован? if ($oldS3Path === $newS3Path) { writeLog(" ⏭️ Уже мигрирован, пропускаю"); $stats['skipped']++; continue; } writeLog(" БЫЛО: $oldS3Path"); writeLog(" БУДЕТ: $newS3Path"); if ($dryRun) { writeLog(" [DRY-RUN] ✓ Будет скопировано"); $stats['success']++; continue; } // РЕАЛЬНОЕ КОПИРОВАНИЕ try { // Проверяем старый файл $head = $s3->headObject(['Bucket' => $bucket, 'Key' => $oldS3Path]); $oldSize = $head['ContentLength']; writeLog(" ✓ Найден, размер: " . number_format($oldSize / 1024, 2) . " KB"); // Копируем $s3->copyObject([ 'Bucket' => $bucket, 'CopySource' => "$bucket/$oldS3Path", 'Key' => $newS3Path, ]); // Проверяем копию $headNew = $s3->headObject(['Bucket' => $bucket, 'Key' => $newS3Path]); $newSize = $headNew['ContentLength']; if ($newSize !== $oldSize) { throw new Exception("Размеры не совпадают!"); } writeLog(" ✅ Скопировано, размер OK"); // Обновляем БД $newUrl = "https://s3.twcstorage.ru/$bucket/$newS3Path"; $adb->pquery("UPDATE vtiger_notes SET filename = ? WHERE notesid = ?", [$newUrl, $docId]); writeLog(" ✅ БД обновлена"); writeLog(" ✅ УСПЕХ!"); $stats['success']++; } catch (Exception $e) { writeLog(" ❌ ОШИБКА: " . $e->getMessage()); $stats['errors']++; } } writeLog("\n📊 === ИТОГО ==="); writeLog("Всего: {$stats['total']}"); writeLog("Успешно: {$stats['success']}"); writeLog("Ошибок: {$stats['errors']}"); writeLog("Пропущено: {$stats['skipped']}"); writeLog("\n✅ Лог: $logFile");