✨ 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!
229 lines
9.0 KiB
PHP
229 lines
9.0 KiB
PHP
<?php
|
||
/**
|
||
* Миграция файлов тикетов (HelpDesk) в новую структуру
|
||
* Перемещает файлы из Documents/documentID/ в Documents/HelpDesk/ticketNo_ticketID/
|
||
*/
|
||
|
||
// Подключаем необходимые файлы
|
||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/config.inc.php';
|
||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/include/database/PearDatabase.php';
|
||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/FilePathManager.php';
|
||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/S3Client.php';
|
||
|
||
// Загружаем переменные окружения
|
||
$envFile = '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env';
|
||
if (file_exists($envFile)) {
|
||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||
foreach ($lines as $line) {
|
||
if (strpos($line, '=') !== false && strpos($line, '#') !== 0) {
|
||
list($key, $value) = explode('=', $line, 2);
|
||
$_ENV[trim($key)] = trim($value);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Подключаем Composer autoloader для AWS SDK
|
||
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
|
||
|
||
use Aws\S3\S3Client;
|
||
use Aws\Exception\AwsException;
|
||
|
||
echo "🚀 Начинаем миграцию файлов тикетов (HelpDesk)...\n\n";
|
||
|
||
// Устанавливаем кодировку UTF-8
|
||
mb_internal_encoding('UTF-8');
|
||
|
||
try {
|
||
// Инициализируем S3 клиент
|
||
$s3Client = new S3Client([
|
||
'version' => 'latest',
|
||
'region' => 'ru-1',
|
||
'endpoint' => 'https://s3.twcstorage.ru',
|
||
'credentials' => [
|
||
'key' => $_ENV['S3_ACCESS_KEY'],
|
||
'secret' => $_ENV['S3_SECRET_KEY'],
|
||
],
|
||
'use_path_style_endpoint' => true,
|
||
]);
|
||
|
||
echo "✅ S3 клиент инициализирован\n";
|
||
|
||
// Подключаемся к базе данных
|
||
$pdo = new PDO("mysql:host={$dbconfig['db_server']};dbname={$dbconfig['db_name']};charset=utf8", $dbconfig['db_username'], $dbconfig['db_password']);
|
||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||
echo "✅ Подключение к БД установлено\n\n";
|
||
|
||
// Находим все файлы тикетов в старой структуре
|
||
$sql = "
|
||
SELECT
|
||
n.notesid,
|
||
n.title,
|
||
n.filename,
|
||
n.s3_key,
|
||
t.ticketid,
|
||
t.ticket_no,
|
||
t.title as ticket_title
|
||
FROM vtiger_notes n
|
||
INNER JOIN vtiger_senotesrel sr ON n.notesid = sr.notesid
|
||
INNER JOIN vtiger_troubletickets t ON sr.crmid = t.ticketid
|
||
WHERE n.filelocationtype = 'E'
|
||
AND n.s3_key IS NOT NULL
|
||
AND n.s3_key LIKE '%/Documents/%'
|
||
AND n.s3_key NOT LIKE '%/Project/%'
|
||
AND n.s3_key NOT LIKE '%/Contacts/%'
|
||
AND n.s3_key NOT LIKE '%/Accounts/%'
|
||
AND n.s3_key NOT LIKE '%/HelpDesk/%'
|
||
ORDER BY t.ticketid, n.notesid
|
||
";
|
||
|
||
$stmt = $pdo->prepare($sql);
|
||
$stmt->execute();
|
||
$files = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
echo "📊 Найдено файлов тикетов для миграции: " . count($files) . "\n\n";
|
||
|
||
if (empty($files)) {
|
||
echo "✅ Все файлы тикетов уже мигрированы!\n";
|
||
exit(0);
|
||
}
|
||
|
||
$migratedCount = 0;
|
||
$errorCount = 0;
|
||
$currentTicketId = null;
|
||
$ticketCount = 0;
|
||
$bucket = $_ENV['S3_BUCKET'];
|
||
|
||
foreach ($files as $file) {
|
||
$notesId = $file['notesid'];
|
||
$title = $file['title'];
|
||
$oldS3Key = $file['s3_key'];
|
||
$ticketId = $file['ticketid'];
|
||
$ticketNo = $file['ticket_no'];
|
||
$ticketTitle = $file['ticket_title'];
|
||
|
||
// Считаем тикеты
|
||
if ($currentTicketId !== $ticketId) {
|
||
$currentTicketId = $ticketId;
|
||
$ticketCount++;
|
||
}
|
||
|
||
echo "🎫 Тикет: {$ticketNo} - {$ticketTitle} (ID: {$ticketId})\n";
|
||
echo " 📄 Файл: {$title} (ID: {$notesId})\n";
|
||
echo " 🔄 Старый путь: {$oldS3Key}\n";
|
||
|
||
try {
|
||
// Простая нормализация имени тикета
|
||
$normalizedTicketNo = preg_replace('/[^a-zA-Z0-9\-_]/u', '_', $ticketNo);
|
||
$normalizedTicketNo = preg_replace('/_+/', '_', $normalizedTicketNo);
|
||
$normalizedTicketNo = trim($normalizedTicketNo, '_');
|
||
|
||
if (empty($normalizedTicketNo)) {
|
||
$normalizedTicketNo = "ticket_{$ticketId}";
|
||
}
|
||
|
||
// Простая нормализация имени файла
|
||
$normalizedTitle = preg_replace('/[^a-zA-Zа-яА-Я0-9\s\-_\.]/u', '', $title);
|
||
$normalizedTitle = preg_replace('/\s+/', '_', trim($normalizedTitle));
|
||
$normalizedTitle = preg_replace('/_+/', '_', $normalizedTitle);
|
||
$normalizedTitle = trim($normalizedTitle, '_');
|
||
|
||
if (empty($normalizedTitle)) {
|
||
$normalizedTitle = "file_{$notesId}";
|
||
}
|
||
|
||
// Получаем расширение файла
|
||
$extension = pathinfo($normalizedTitle, PATHINFO_EXTENSION);
|
||
if (empty($extension)) {
|
||
$extension = 'pdf';
|
||
}
|
||
|
||
// Формируем новый путь
|
||
$newS3Key = "crm2/CRM_Active_Files/Documents/HelpDesk/{$normalizedTicketNo}_{$ticketId}/{$normalizedTitle}_{$notesId}.{$extension}";
|
||
|
||
echo " ✅ Новый путь: {$newS3Key}\n";
|
||
|
||
// Проверяем существование файла в S3
|
||
$oldS3Key = ltrim($oldS3Key, '/');
|
||
|
||
try {
|
||
$s3Client->headObject([
|
||
'Bucket' => $bucket,
|
||
'Key' => $oldS3Key
|
||
]);
|
||
echo " ✅ Файл найден в S3\n";
|
||
|
||
// Копируем файл в новое место
|
||
$s3Client->copyObject([
|
||
'Bucket' => $bucket,
|
||
'CopySource' => $bucket . '/' . $oldS3Key,
|
||
'Key' => $newS3Key
|
||
]);
|
||
echo " ✅ Файл скопирован в новое место\n";
|
||
|
||
// Проверяем что новый файл существует
|
||
$s3Client->headObject([
|
||
'Bucket' => $bucket,
|
||
'Key' => $newS3Key
|
||
]);
|
||
echo " ✅ Новый файл проверен\n";
|
||
|
||
// Удаляем старый файл
|
||
$s3Client->deleteObject([
|
||
'Bucket' => $bucket,
|
||
'Key' => $oldS3Key
|
||
]);
|
||
echo " ✅ Старый файл удален\n";
|
||
|
||
// Обновляем записи в БД
|
||
$newFilename = 'https://s3.twcstorage.ru/' . $bucket . '/' . $newS3Key;
|
||
|
||
$updateSql = "
|
||
UPDATE vtiger_notes
|
||
SET s3_key = ?, filename = ?
|
||
WHERE notesid = ?
|
||
";
|
||
|
||
$updateStmt = $pdo->prepare($updateSql);
|
||
$updateStmt->execute([$newS3Key, $newFilename, $notesId]);
|
||
|
||
echo " ✅ Записи в БД обновлены\n";
|
||
$migratedCount++;
|
||
|
||
} catch (AwsException $e) {
|
||
if ($e->getAwsErrorCode() === 'NotFound') {
|
||
echo " ❌ Файл не найден в S3: {$oldS3Key}\n";
|
||
} else {
|
||
echo " ❌ Ошибка S3: " . $e->getMessage() . "\n";
|
||
}
|
||
$errorCount++;
|
||
}
|
||
|
||
} catch (Exception $e) {
|
||
echo " ❌ Ошибка: " . $e->getMessage() . "\n";
|
||
$errorCount++;
|
||
}
|
||
|
||
echo "\n";
|
||
}
|
||
|
||
echo "🎉 МИГРАЦИЯ ЗАВЕРШЕНА!\n";
|
||
echo "📊 Статистика:\n";
|
||
echo " • Тикетов обработано: {$ticketCount}\n";
|
||
echo " • Файлов мигрировано: {$migratedCount}\n";
|
||
echo " • Ошибок: {$errorCount}\n";
|
||
echo " • Всего файлов: " . count($files) . "\n";
|
||
|
||
if ($errorCount > 0) {
|
||
echo "\n⚠️ Некоторые файлы не удалось мигрировать. Возможные причины:\n";
|
||
echo " • Файлы отсутствуют в S3\n";
|
||
echo " • Проблемы с правами доступа\n";
|
||
echo " • Ошибки сети\n";
|
||
}
|
||
|
||
} catch (Exception $e) {
|
||
echo "❌ КРИТИЧЕСКАЯ ОШИБКА: " . $e->getMessage() . "\n";
|
||
echo "Стек вызовов:\n" . $e->getTraceAsString() . "\n";
|
||
exit(1);
|
||
}
|
||
|