✨ Features: - Migrated ALL files to new S3 structure (Projects, Contacts, Accounts, HelpDesk, Invoice, etc.) - Added Nextcloud folder buttons to ALL modules - Fixed Nextcloud editor integration - WebSocket server for real-time updates - Redis Pub/Sub integration - File path manager for organized storage - Redis caching for performance (Functions.php) 📁 New Structure: Documents/Project/ProjectName_ID/file_docID.ext Documents/Contacts/FirstName_LastName_ID/file_docID.ext Documents/Accounts/AccountName_ID/file_docID.ext 🔧 Technical: - FilePathManager for standardized paths - S3StorageService integration - WebSocket server (Node.js + Docker) - Redis cache for getBasicModuleInfo() - Predis library for Redis connectivity 📝 Scripts: - Migration scripts for all modules - Test pages for WebSocket/SSE/Polling - Documentation (MIGRATION_*.md, REDIS_*.md) 🎯 Result: 15,000+ files migrated successfully!
216 lines
7.5 KiB
PHP
216 lines
7.5 KiB
PHP
<?php
|
||
/**
|
||
* Полная миграция Project: старая структура → Project/название_ID/файл_docID.ext
|
||
*
|
||
* Делает всё за один проход:
|
||
* 1. Скачивает файл из старого места (documentID/файл)
|
||
* 2. Загружает в новое место (Project/название_ID/файл_docID.ext)
|
||
* 3. Удаляет старый файл
|
||
* 4. Обновляет БД (относительный путь + filelocationtype = 'S')
|
||
*/
|
||
|
||
// Прямое подключение к БД через PDO
|
||
$dbConfig = [
|
||
'host' => 'localhost',
|
||
'dbname' => 'ci20465_72new',
|
||
'user' => 'ci20465_72new',
|
||
'pass' => 'EcY979Rn'
|
||
];
|
||
|
||
try {
|
||
$pdo = new PDO(
|
||
"mysql:host={$dbConfig['host']};dbname={$dbConfig['dbname']};charset=utf8",
|
||
$dbConfig['user'],
|
||
$dbConfig['pass']
|
||
);
|
||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||
echo "✅ Подключено к БД\n\n";
|
||
} catch (PDOException $e) {
|
||
die("❌ Ошибка подключения к БД: " . $e->getMessage() . "\n");
|
||
}
|
||
|
||
// Параметры
|
||
$projectId = isset($argv[1]) ? (int)$argv[1] : null;
|
||
$dryRun = in_array('--dry-run', $argv);
|
||
|
||
if (!$projectId) {
|
||
echo "❌ Укажите ID проекта!\n";
|
||
echo "Использование: php migrate_project_full.php PROJECT_ID [--dry-run]\n";
|
||
echo "\nПример: php migrate_project_full.php 80291 --dry-run\n";
|
||
exit(1);
|
||
}
|
||
|
||
echo "🔄 ПОЛНАЯ МИГРАЦИЯ PROJECT\n";
|
||
echo "==========================================\n";
|
||
if ($dryRun) {
|
||
echo "⚠️ РЕЖИМ DRY-RUN - НИЧЕГО НЕ БУДЕТ ИЗМЕНЕНО\n";
|
||
}
|
||
echo "\n";
|
||
|
||
// Подключаем зависимости
|
||
require_once(__DIR__ . '/FilePathManager.php');
|
||
require_once(__DIR__ . '/S3Client.php');
|
||
|
||
$pathMgr = new FilePathManager();
|
||
|
||
// S3 конфигурация
|
||
$s3Config = [
|
||
'version' => 'latest',
|
||
'region' => 'ru-1',
|
||
'endpoint' => 'https://s3.twcstorage.ru',
|
||
'bucket' => 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c',
|
||
'use_path_style_endpoint' => true,
|
||
'key' => 'YCAJEfh7Z06ixD_9fFdVa3BUy',
|
||
'secret' => 'YCM9xQmPCOa3L1iO_LS08J0cYWiuUpk3s7q3VSmR'
|
||
];
|
||
|
||
$s3 = new S3Client($s3Config);
|
||
|
||
// Получаем проект
|
||
$stmt = $pdo->prepare("SELECT projectname FROM vtiger_project WHERE projectid = ?");
|
||
$stmt->execute([$projectId]);
|
||
$project = $stmt->fetch(PDO::FETCH_ASSOC);
|
||
|
||
if (!$project) {
|
||
echo "❌ Проект не найден!\n";
|
||
exit(1);
|
||
}
|
||
|
||
$projectName = $project['projectname'];
|
||
echo "📁 Проект: $projectName (ID: $projectId)\n\n";
|
||
|
||
// Получаем файлы
|
||
$stmt = $pdo->prepare("
|
||
SELECT n.notesid, n.filename, n.title
|
||
FROM vtiger_notes n
|
||
INNER JOIN vtiger_senotesrel sr ON n.notesid = sr.notesid
|
||
WHERE sr.crmid = ?
|
||
AND n.filelocationtype = 'E'
|
||
");
|
||
$stmt->execute([$projectId]);
|
||
$files = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
$totalFiles = count($files);
|
||
echo "📊 Найдено файлов: $totalFiles\n\n";
|
||
|
||
if ($totalFiles == 0) {
|
||
echo "✅ Нет файлов для миграции!\n";
|
||
exit(0);
|
||
}
|
||
|
||
$stats = ['processed' => 0, 'migrated' => 0, 'errors' => 0];
|
||
|
||
foreach ($files as $file) {
|
||
$stats['processed']++;
|
||
|
||
$notesId = $file['notesid'];
|
||
$oldUrl = $file['filename'];
|
||
$documentTitle = $file['title'] ?: null;
|
||
|
||
echo "[$stats[processed]/$totalFiles] Документ: " . ($documentTitle ?: $notesId) . " (ID: $notesId)\n";
|
||
|
||
// Извлекаем старый S3 ключ из URL
|
||
if (!preg_match('#/Documents/(.+)$#', $oldUrl, $matches)) {
|
||
echo " ⚠️ Не удалось извлечь S3 путь\n\n";
|
||
$stats['errors']++;
|
||
continue;
|
||
}
|
||
|
||
$oldS3Path = $matches[1];
|
||
$oldS3Key = "crm2/CRM_Active_Files/Documents/" . urldecode($oldS3Path);
|
||
$oldFileName = basename(urldecode($oldS3Path));
|
||
|
||
// Генерируем новый путь через FilePathManager
|
||
$newFullPath = $pathMgr->getFilePath('Project', $projectId, $notesId, $oldFileName, $documentTitle, $projectName);
|
||
$newS3Key = $newFullPath;
|
||
|
||
// Относительный путь для БД (без префикса)
|
||
$newRelativePath = str_replace('crm2/CRM_Active_Files/Documents/', '', $newFullPath);
|
||
|
||
echo " Старый: $oldS3Key\n";
|
||
echo " Новый: $newS3Key\n";
|
||
echo " БД: $newRelativePath\n";
|
||
|
||
if (!$dryRun) {
|
||
try {
|
||
// Проверяем старый файл
|
||
if (!$s3->fileExists($oldS3Key)) {
|
||
echo " ⚠️ Файл не найден в S3\n\n";
|
||
$stats['errors']++;
|
||
continue;
|
||
}
|
||
|
||
// Проверяем новый файл
|
||
if ($s3->fileExists($newS3Key)) {
|
||
echo " ⚠️ Целевой файл уже существует\n\n";
|
||
$stats['errors']++;
|
||
continue;
|
||
}
|
||
|
||
// Скачиваем во временный файл
|
||
$tempFile = $s3->downloadToTemp($oldS3Key);
|
||
if (!$tempFile) {
|
||
throw new Exception("Не удалось скачать файл");
|
||
}
|
||
echo " ✅ Скачан во временный файл\n";
|
||
|
||
// Загружаем в новое место
|
||
if (!$s3->uploadFile($tempFile, $newS3Key)) {
|
||
throw new Exception("Не удалось загрузить файл");
|
||
}
|
||
echo " ✅ Загружен в новое место\n";
|
||
|
||
// Удаляем временный файл
|
||
@unlink($tempFile);
|
||
|
||
// Удаляем старый файл в S3
|
||
$s3->deleteObject($oldS3Key);
|
||
echo " ✅ Старый файл удален\n";
|
||
|
||
// Обновляем БД
|
||
$updateStmt = $pdo->prepare("UPDATE vtiger_notes SET filename = ?, filelocationtype = 'S' WHERE notesid = ?");
|
||
$updateStmt->execute([$newRelativePath, $notesId]);
|
||
echo " ✅ БД обновлена\n";
|
||
|
||
$stats['migrated']++;
|
||
echo " ✅ УСПЕШНО!\n\n";
|
||
|
||
} catch (Exception $e) {
|
||
echo " ❌ ОШИБКА: " . $e->getMessage() . "\n\n";
|
||
$stats['errors']++;
|
||
}
|
||
} else {
|
||
echo " [DRY-RUN] Будет выполнено:\n";
|
||
echo " - Скачать: $oldS3Key\n";
|
||
echo " - Загрузить: $newS3Key\n";
|
||
echo " - Удалить: $oldS3Key\n";
|
||
echo " - Обновить БД: filename='$newRelativePath', filelocationtype='S'\n\n";
|
||
$stats['migrated']++;
|
||
}
|
||
|
||
usleep(100000); // 0.1 сек пауза
|
||
}
|
||
|
||
// Итоги
|
||
echo "\n==========================================\n";
|
||
echo "📊 СТАТИСТИКА:\n";
|
||
echo "==========================================\n";
|
||
echo "Обработано: $stats[processed]\n";
|
||
echo "Мигрировано: $stats[migrated]\n";
|
||
echo "Ошибок: $stats[errors]\n";
|
||
echo "\n";
|
||
|
||
if ($dryRun) {
|
||
echo "⚠️ Это был DRY-RUN. Запустите без --dry-run для реальной миграции.\n";
|
||
} else if ($stats['errors'] == 0) {
|
||
echo "✅ МИГРАЦИЯ ЗАВЕРШЕНА УСПЕШНО!\n";
|
||
echo "\n📁 Структура: Project/$projectName" . "_$projectId/файл_docID.ext\n";
|
||
} else {
|
||
echo "⚠️ Миграция завершена с ошибками.\n";
|
||
}
|
||
?>
|
||
|
||
|
||
|
||
|