Files
crm.clientright.ru/crm_extensions/file_storage/migrate_with_project_name.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

173 lines
5.9 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
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:']);
$dryRun = isset($options['dry-run']);
$projectId = isset($options['project']) ? (int)$options['project'] : null;
if (!$projectId) {
die("❌ Укажите --project=ID\n");
}
$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('Ymd_His') . '.log';
if (!is_dir(__DIR__ . '/logs')) {
mkdir(__DIR__ . '/logs', 0755, true);
}
function writeLog($msg) {
global $logFile;
$line = "[" . date('Y-m-d H:i:s') . "] $msg\n";
file_put_contents($logFile, $line, FILE_APPEND);
echo $msg . "\n";
}
function sanitizeFolderName($name) {
// Убираем проблемные символы для папки
$name = str_replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|', '#'], '_', $name);
// Множественные пробелы → один пробел
$name = preg_replace('/\s+/', ' ', $name);
// Заменяем пробелы на подчёркивания
$name = str_replace(' ', '_', $name);
return trim($name);
}
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 projectname FROM vtiger_project WHERE projectid = ?";
$result = $adb->pquery($sql, [$projectId]);
if ($adb->num_rows($result) === 0) {
die("❌ Проект $projectId не найден!\n");
}
$projectRow = $adb->fetchByAssoc($result);
$projectName = sanitizeFolderName($projectRow['projectname']);
writeLog("📋 Название проекта: {$projectRow['projectname']}");
writeLog("📁 Папка: {$projectName}_{$projectId}");
// Получаем документы проекта
$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\n");
$newFolderPath = "crm2/CRM_Active_Files/Documents/{$projectName}_{$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("📄 [$docId] {$doc['title']}");
// Извлекаем S3 путь
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";
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]);
if ($headNew['ContentLength'] !== $oldSize) {
throw new Exception("Размеры не совпадают!");
}
writeLog(" ✅ Скопировано");
// Обновляем БД
$newUrl = "https://s3.twcstorage.ru/$bucket/$newS3Path";
$adb->pquery("UPDATE vtiger_notes SET filename = ? WHERE notesid = ?", [$newUrl, $docId]);
writeLog(" ✅ БД обновлена");
$stats['success']++;
} catch (Exception $e) {
writeLog(" ❌ ОШИБКА: " . $e->getMessage());
$stats['errors']++;
}
}
writeLog("\n📊 === ИТОГО ===");
writeLog("Успешно: {$stats['success']} / {$stats['total']}");
writeLog("Ошибок: {$stats['errors']}");
writeLog("Пропущено: {$stats['skipped']}");
writeLog("✅ Лог: $logFile");