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

265 lines
7.9 KiB
PHP

<?php
/**
* Webhook endpoint для получения событий от Nextcloud
*
* Настройка в Nextcloud:
* - Webhook URL: https://crm.clientright.ru/crm_extensions/file_storage/api/nextcloud_webhook.php
* - События: file_created, file_updated, file_deleted, folder_renamed, folder_deleted
*/
// Подключаем CRM
require_once('../../../../config.inc.php');
require_once('../../../../include/utils/utils.php');
require_once('../../../../include/utils/CommonUtils.php');
require_once('../FilePathManager.php');
// Логирование
$logFile = '/var/log/crm_nextcloud_webhook.log';
function logWebhook($message) {
global $logFile;
$timestamp = date('Y-m-d H:i:s');
file_put_contents($logFile, "[$timestamp] $message\n", FILE_APPEND | LOCK_EX);
}
// Проверяем метод запроса
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
exit;
}
// Получаем данные webhook
$input = file_get_contents('php://input');
$data = json_decode($input, true);
logWebhook("Webhook received: " . $input);
if (!$data) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON']);
exit;
}
// Проверяем обязательные поля
if (!isset($data['action']) || !isset($data['file_path'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing required fields']);
exit;
}
$action = $data['action'];
$filePath = $data['file_path'];
$projectId = isset($data['project_id']) ? $data['project_id'] : null;
logWebhook("Processing action: $action, path: $filePath, project: $projectId");
// Парсим путь файла
$pathManager = new FilePathManager();
$parsedPath = $pathManager->parseFilePath($filePath);
if (!$parsedPath) {
logWebhook("Failed to parse file path: $filePath");
http_response_code(400);
echo json_encode(['error' => 'Invalid file path']);
exit;
}
$module = $parsedPath['module'];
$recordId = $parsedPath['recordId'];
$documentId = $parsedPath['documentId'];
$fileName = $parsedPath['fileName'];
logWebhook("Parsed: module=$module, recordId=$recordId, documentId=$documentId, fileName=$fileName");
// Обрабатываем разные типы событий
switch ($action) {
case 'file_created':
handleFileCreated($module, $recordId, $documentId, $fileName, $data);
break;
case 'file_updated':
handleFileUpdated($module, $recordId, $documentId, $fileName, $data);
break;
case 'file_deleted':
handleFileDeleted($module, $recordId, $documentId, $fileName, $data);
break;
case 'folder_renamed':
handleFolderRenamed($module, $recordId, $data);
break;
case 'folder_deleted':
handleFolderDeleted($module, $recordId, $data);
break;
default:
logWebhook("Unknown action: $action");
http_response_code(400);
echo json_encode(['error' => 'Unknown action']);
exit;
}
// Функция обработки создания файла
function handleFileCreated($module, $recordId, $documentId, $fileName, $data) {
global $adb;
// Проверяем, есть ли уже запись в БД
$query = "SELECT notesid FROM vtiger_notes WHERE notesid = ?";
$result = $adb->pquery($query, [$documentId]);
if ($adb->num_rows($result) > 0) {
logWebhook("File already exists in DB: $documentId");
return;
}
// Создаем новую запись в БД
$query = "INSERT INTO vtiger_notes (notesid, title, filename, filetype, filesize, filelocationtype, fileversion, createdtime, modifiedtime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$title = pathinfo($fileName, PATHINFO_FILENAME);
$fileType = pathinfo($fileName, PATHINFO_EXTENSION);
$fileSize = isset($data['file_size']) ? $data['file_size'] : 0;
$now = date('Y-m-d H:i:s');
$adb->pquery($query, [
$documentId,
$title,
$fileName,
$fileType,
$fileSize,
'I', // Internal
'1',
$now,
$now
]);
// Отправляем SSE событие
sendSSEEvent('file_created', [
'module' => $module,
'recordId' => $recordId,
'documentId' => $documentId,
'fileName' => $fileName
]);
logWebhook("File created in DB: $documentId");
}
// Функция обработки обновления файла
function handleFileUpdated($module, $recordId, $documentId, $fileName, $data) {
global $adb;
// Обновляем запись в БД
$query = "UPDATE vtiger_notes SET filename = ?, filesize = ?, modifiedtime = ? WHERE notesid = ?";
$fileSize = isset($data['file_size']) ? $data['file_size'] : 0;
$now = date('Y-m-d H:i:s');
$adb->pquery($query, [
$fileName,
$fileSize,
$now,
$documentId
]);
// Отправляем SSE событие
sendSSEEvent('file_updated', [
'module' => $module,
'recordId' => $recordId,
'documentId' => $documentId,
'fileName' => $fileName
]);
logWebhook("File updated in DB: $documentId");
}
// Функция обработки удаления файла
function handleFileDeleted($module, $recordId, $documentId, $fileName, $data) {
global $adb;
// Помечаем файл как удаленный
$query = "UPDATE vtiger_notes SET deleted = 1 WHERE notesid = ?";
$adb->pquery($query, [$documentId]);
// Отправляем SSE событие
sendSSEEvent('file_deleted', [
'module' => $module,
'recordId' => $recordId,
'documentId' => $documentId,
'fileName' => $fileName
]);
logWebhook("File deleted in DB: $documentId");
}
// Функция обработки переименования папки
function handleFolderRenamed($module, $recordId, $data) {
global $adb;
$oldPath = $data['old_path'];
$newPath = $data['new_path'];
// Обновляем пути файлов в БД
$query = "UPDATE vtiger_notes SET filename = REPLACE(filename, ?, ?) WHERE filename LIKE ?";
$adb->pquery($query, [$oldPath, $newPath, "%$oldPath%"]);
// Отправляем SSE событие
sendSSEEvent('folder_renamed', [
'module' => $module,
'recordId' => $recordId,
'oldPath' => $oldPath,
'newPath' => $newPath
]);
logWebhook("Folder renamed: $oldPath -> $newPath");
}
// Функция обработки удаления папки
function handleFolderDeleted($module, $recordId, $data) {
global $adb;
$folderPath = $data['folder_path'];
// Помечаем все файлы папки как удаленные
$query = "UPDATE vtiger_notes SET deleted = 1 WHERE filename LIKE ?";
$adb->pquery($query, ["%$folderPath%"]);
// Отправляем SSE событие
sendSSEEvent('folder_deleted', [
'module' => $module,
'recordId' => $recordId,
'folderPath' => $folderPath
]);
logWebhook("Folder deleted: $folderPath");
}
// Функция для отправки SSE события
function sendSSEEvent($type, $data) {
$event = [
'type' => $type,
'data' => $data,
'timestamp' => time()
];
// Сохраняем событие в файл для SSE endpoint
$eventsFile = '/tmp/crm_sse_events.json';
$events = [];
if (file_exists($eventsFile)) {
$events = json_decode(file_get_contents($eventsFile), true) ?: [];
}
$events[] = $event;
file_put_contents($eventsFile, json_encode($events));
}
// Отправляем успешный ответ
http_response_code(200);
echo json_encode(['status' => 'success', 'message' => 'Event processed']);
?>