Files
crm.clientright.ru/crm_extensions/file_storage/migrate_project_files_v2.php
Fedor 9245768987 🚀 CRM Files Migration & Real-time Features
 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!
2025-10-24 19:59:28 +03:00

235 lines
8.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* БЕЗОПАСНАЯ МИГРАЦИЯ ФАЙЛОВ ПРОЕКТА В НОВУЮ СТРУКТУРУ (v2)
* ИСПРАВЛЕНИЕ: Декодирование URL-encoded путей
*/
chdir('/var/www/fastuser/data/www/crm.clientright.ru');
require_once 'include/utils/utils.php';
require_once 'include/database/PearDatabase.php';
require_once 'crm_extensions/vendor/autoload.php';
use Aws\S3\S3Client as AwsS3Client;
global $adb;
$options = getopt('', ['dry-run', 'project:', 'batch:', 'all']);
$dryRun = isset($options['dry-run']);
$projectId = isset($options['project']) ? (int)$options['project'] : null;
$s3 = new AwsS3Client([
'version' => '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_' . date('Y-m-d_H-i-s') . '.log';
if (!is_dir(__DIR__ . '/logs')) {
mkdir(__DIR__ . '/logs', 0755, true);
}
function writeLog($message, $toScreen = true) {
global $logFile;
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[$timestamp] $message\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
if ($toScreen) {
echo $message . "\n";
}
}
function sanitizeFileName($name) {
$name = str_replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|'], '_', $name);
$name = preg_replace('/\s+/', ' ', $name);
return trim($name);
}
function extractExtension($fileName) {
$parts = explode('.', basename($fileName));
return count($parts) > 1 ? array_pop($parts) : '';
}
function migrateProject($projectId, $dryRun = false) {
global $adb, $s3, $bucket;
writeLog("🔍 === МИГРАЦИЯ ПРОЕКТА $projectId ===");
if ($dryRun) {
writeLog("⚠️ РЕЖИМ DRY-RUN - изменения НЕ будут применены");
}
$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");
if ($count === 0) {
writeLog("⚠️ Нет документов для миграции");
return;
}
$newFolderPath = "crm2/CRM_Active_Files/Documents/проекта_{$projectId}";
writeLog("📁 Новая папка: $newFolderPath");
$stats = [
'total' => $count,
'success' => 0,
'errors' => 0,
];
$usedNames = [];
for ($i = 0; $i < $count; $i++) {
$doc = $adb->fetchByAssoc($result);
$docId = $doc['notesid'];
$title = sanitizeFileName($doc['title']);
$oldFileName = $doc['filename'];
writeLog("\n📄 Документ $docId: {$doc['title']}");
// Извлекаем путь из URL и ДЕКОДИРУЕМ
$oldS3Path = null;
if (strpos($oldFileName, 'https://s3.twcstorage.ru/') === 0) {
$oldS3Path = str_replace("https://s3.twcstorage.ru/$bucket/", '', $oldFileName);
// ВАЖНО: Декодируем URL-encoded символы
$oldS3Path = urldecode($oldS3Path);
} elseif (strpos($oldFileName, 'crm2/') === 0) {
$oldS3Path = urldecode($oldFileName);
}
if (!$oldS3Path) {
writeLog("Не удалось определить старый путь S3");
$stats['errors']++;
continue;
}
writeLog(" Старый S3 путь: $oldS3Path");
$extension = extractExtension($oldFileName);
$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";
writeLog(" Новый S3 путь: $newS3Path");
if ($dryRun) {
writeLog(" [DRY-RUN] ✓ Будет скопировано");
$stats['success']++;
continue;
}
// РЕАЛЬНАЯ МИГРАЦИЯ
try {
// Проверяем старый файл
$headObject = $s3->headObject([
'Bucket' => $bucket,
'Key' => $oldS3Path,
]);
$oldSize = $headObject['ContentLength'];
writeLog(" ✓ Старый файл найден, размер: " . number_format($oldSize / 1024, 2) . " KB");
// Копируем
writeLog(" 📋 Копирую файл...");
$s3->copyObject([
'Bucket' => $bucket,
'CopySource' => "$bucket/$oldS3Path",
'Key' => $newS3Path,
]);
// Проверяем копию
$headNewObject = $s3->headObject([
'Bucket' => $bucket,
'Key' => $newS3Path,
]);
$newSize = $headNewObject['ContentLength'];
if ($newSize !== $oldSize) {
throw new Exception("Размер не совпадает! Старый: $oldSize, Новый: $newSize");
}
writeLog(" ✅ Файл скопирован, размер совпадает: " . number_format($newSize / 1024, 2) . " KB");
// Обновляем БД
$newUrl = "https://s3.twcstorage.ru/$bucket/$newS3Path";
$updateSql = "UPDATE vtiger_notes SET filename = ? WHERE notesid = ?";
$adb->pquery($updateSql, [$newUrl, $docId]);
writeLog(" ✅ База данных обновлена");
writeLog(" ✅ УСПЕХ! Документ $docId мигрирован");
$stats['success']++;
} catch (Exception $e) {
writeLog(" ❌ ОШИБКА: " . $e->getMessage());
$stats['errors']++;
try {
$s3->deleteObject(['Bucket' => $bucket, 'Key' => $newS3Path]);
writeLog(" 🗑️ Частичная копия удалена");
} catch (Exception $cleanupError) {
// Игнорируем
}
}
}
writeLog("\n📊 === СТАТИСТИКА МИГРАЦИИ ===");
writeLog("Всего документов: {$stats['total']}");
writeLog("Успешно: {$stats['success']}");
writeLog("Ошибок: {$stats['errors']}");
return $stats;
}
writeLog("🚀 === СТАРТ МИГРАЦИИ ФАЙЛОВ (v2) ===");
writeLog("Время: " . date('Y-m-d H:i:s'));
if ($dryRun) {
writeLog("\n⚠️⚠️⚠️ РЕЖИМ DRY-RUN - НИЧЕГО НЕ БУДЕТ ИЗМЕНЕНО ⚠️⚠️⚠️\n");
}
if (!$dryRun) {
writeLog("\n💾 === СОЗДАНИЕ РЕЗЕРВНОЙ КОПИИ БД ===");
$backupFile = "backup_before_migration_" . date('Y-m-d_H-i-s') . ".sql";
$backupCmd = "mysqldump -u ci20465_72new -p'EcY979Rn' ci20465_72new vtiger_notes vtiger_senotesrel > $backupFile 2>&1";
exec($backupCmd, $output, $returnCode);
if (file_exists($backupFile) && filesize($backupFile) > 0) {
writeLog("✅ Резервная копия создана: $backupFile");
} else {
writeLog("❌ ОШИБКА создания резервной копии!");
writeLog("🛑 МИГРАЦИЯ ОТМЕНЕНА!");
exit(1);
}
}
if ($projectId) {
writeLog("\n🎯 Миграция проекта: $projectId");
migrateProject($projectId, $dryRun);
} else {
writeLog("\n❌ Укажите --project=ID");
exit(1);
}
writeLog("\n✅ === МИГРАЦИЯ ЗАВЕРШЕНА ===");
writeLog("Лог: $logFile");