Files
crm.clientright.ru/upload_file_crm_v2.php

526 lines
22 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
error_reporting(E_ALL);
ini_set('display_errors', '1');
// Для POST запросов отключаем вывод ошибок в HTML
$IS_POST = ($_SERVER['REQUEST_METHOD'] === 'POST');
if ($IS_POST) {
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
}
// Функция для записи в лог с детализацией
function writeLog($message, $level = 'INFO') {
$timestamp = date('Y-m-d H:i:s');
$line = "[$timestamp] [$level] $message\n";
// Пробуем писать в основной лог
@file_put_contents(__DIR__ . '/logs/upload_documents_v2_detailed.log', $line, FILE_APPEND | LOCK_EX);
// И в /tmp как запасной вариант
@file_put_contents('/tmp/upload_documents_v2_detailed.log', $line, FILE_APPEND | LOCK_EX);
// И в системный лог
error_log('[upload_documents_v2_detailed] ' . $message);
}
// Функция для JSON ответа
function json_response($data, $code = 200) {
if (!headers_sent()) {
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
}
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
// Быстрый ping
if (isset($_GET['ping'])) {
header('Content-Type: text/plain; charset=utf-8');
echo 'pong v2 detailed';
exit;
}
// Для POST запросов регистрируем обработчик фатальных ошибок
if ($IS_POST) {
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
writeLog('FATAL: ' . $error['message'] . ' in ' . $error['file'] . ':' . $error['line'], 'FATAL');
json_response([
'success' => false,
'error' => [
'type' => 'fatal',
'message' => 'Internal error'
]
], 500);
}
});
}
// Инициализация CRM
require_once 'config.inc.php';
require_once 'include/utils/utils.php';
require_once 'includes/Loader.php';
vimport('includes.runtime.Globals');
require_once 'include/database/PearDatabase.php';
require_once 'modules/Users/Users.php';
require_once 'include/Webservices/Utils.php';
require_once 'include/Webservices/Create.php';
require_once 'include/Webservices/Login.php';
require_once 'include/Webservices/AuthToken.php';
require_once 'include/Webservices/AddRelated.php';
require_once 'data/CRMEntity.php';
require_once 'modules/Vtiger/CRMEntity.php';
$adb = PearDatabase::getInstance();
// Вспомогательные функции
function getUserWsPrefix() {
global $adb;
$rs = $adb->pquery("SELECT id FROM vtiger_ws_entity WHERE name=?", ['Users']);
return ($rs && $adb->num_rows($rs) > 0) ? $adb->query_result($rs, 0, 'id') : 19;
}
function getProjectWsIdFromDB($projectId) {
global $adb;
$rs = $adb->pquery("SELECT id FROM vtiger_ws_entity WHERE name=?", ['Project']);
return ($rs && $adb->num_rows($rs) > 0) ? $adb->query_result($rs, 0, 'id') . 'x' . (int)$projectId : null;
}
function getDocumentFoldersWsPrefix() {
global $adb;
$rs = $adb->pquery("SELECT id FROM vtiger_ws_entity WHERE name=?", ['DocumentFolders']);
return ($rs && $adb->num_rows($rs) > 0) ? (int)$adb->query_result($rs, 0, 'id') : 22;
}
function getFolderWsIdByName($folderName) {
global $adb;
$rs = $adb->pquery('SELECT folderid FROM vtiger_attachmentsfolder WHERE foldername = ? LIMIT 1', [$folderName]);
if ($rs && $adb->num_rows($rs) > 0) {
$folderId = (int)$adb->query_result($rs, 0, 'folderid');
$prefix = getDocumentFoldersWsPrefix();
return $prefix . 'x' . $folderId;
}
return null;
}
/**
* Извлекает S3 метаданные из URL
*/
function parseS3Url($fileUrl) {
$parsed = parse_url($fileUrl);
if (!$parsed || !isset($parsed['host']) || !isset($parsed['path'])) {
return null;
}
$path = ltrim($parsed['path'], '/');
$pathParts = explode('/', $path, 2);
if (count($pathParts) < 2) {
return null;
}
return [
'bucket' => $pathParts[0],
'key' => $pathParts[1],
'host' => $parsed['host']
];
}
/**
* Получает размер файла из S3
*/
function getS3FileSize($fileUrl) {
try {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $fileUrl);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 && $response) {
if (preg_match('/Content-Length:\s*(\d+)/i', $response, $matches)) {
return (int)$matches[1];
}
}
return 0;
} catch (Exception $e) {
writeLog("Ошибка получения размера файла: " . $e->getMessage(), 'ERROR');
return 0;
}
}
/**
* Обновляет документ в базе данных напрямую с S3 метаданными
*/
function updateDocumentS3Metadata($documentId, $s3Info, $fileSize, $originalFileName) {
global $adb;
try {
writeLog("Обновляем S3 метаданные для документа $documentId", 'DEBUG');
writeLog("S3 Bucket: {$s3Info['bucket']}, Key: {$s3Info['key']}, Size: $fileSize", 'DEBUG');
$updateQuery = "
UPDATE vtiger_notes
SET
filename = ?,
filelocationtype = 'E',
filesize = ?,
s3_bucket = ?,
s3_key = ?,
s3_etag = ''
WHERE notesid = ?
";
// Создаем полный S3 URL для отображения в CRM
$s3Url = "https://s3.twcstorage.ru/{$s3Info['bucket']}/{$s3Info['key']}";
$params = [
$s3Url, // Используем полный S3 URL вместо оригинального имени файла
$fileSize,
$s3Info['bucket'],
$s3Info['key'],
$documentId
];
writeLog("SQL: $updateQuery", 'DEBUG');
writeLog("Params: " . json_encode($params), 'DEBUG');
$result = $adb->pquery($updateQuery, $params);
if ($result) {
writeLog("✅ S3 метаданные успешно обновлены для документа $documentId", 'SUCCESS');
return true;
} else {
writeLog("❌ Ошибка обновления S3 метаданных: " . $adb->database->errorMsg(), 'ERROR');
return false;
}
} catch (Exception $e) {
writeLog("❌ Исключение при обновлении S3 метаданных: " . $e->getMessage(), 'ERROR');
return false;
}
}
// 🚀 УЛУЧШЕННАЯ ФУНКЦИЯ: Создание документов с правильными S3 метаданными
function createDocumentsWithSession($data) {
global $adb, $current_user;
$sessionName = $data['sessionName'];
$projectId = (int)$data['projectid'];
$contactId = (int)$data['contactid'];
$userId = (int)($data['user_id'] ?? 1);
$filesArray = $data['files'] ?? [];
writeLog('🚀 Начинаем создание документов с правильными S3 метаданными', 'INFO');
writeLog("📋 Проект: $projectId, Контакт: $contactId, Пользователь: $userId", 'INFO');
writeLog('📋 Файлов к обработке: ' . count($filesArray), 'INFO');
// Упрощенный подход: используем только webservice API без обращений к БД
$projectWsId = '20x' . $projectId; // Предполагаем стандартный префикс для Project
$assignedUserWsId = '19x' . $userId; // Предполагаем стандартный префикс для Users
$folderWsId = '22x1'; // Default папка
writeLog("✅ Project WS ID: $projectWsId", 'DEBUG');
writeLog("👤 User WS ID: $assignedUserWsId", 'DEBUG');
writeLog("📁 Папка: $folderWsId", 'DEBUG');
// Инициализируем CURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, 'https://crm.clientright.ru/webservice.php');
curl_setopt($ch, CURLOPT_POST, 1);
$results = [];
foreach ($filesArray as $i => $file) {
$fileName = $file['original_file_name'] ?? $file['file_name'] ?? 'Unknown';
writeLog("📄 Обрабатываем файл #{$i}: $fileName", 'INFO');
try {
// Проверяем обязательные поля
if (empty($file['file_url'])) {
throw new Exception("Отсутствует file_url");
}
// Парсим S3 URL для получения метаданных
$s3Info = parseS3Url($file['file_url']);
if (!$s3Info) {
throw new Exception("Не удалось распарсить S3 URL: " . $file['file_url']);
}
writeLog("🔍 S3 Bucket: {$s3Info['bucket']}, Key: {$s3Info['key']}", 'DEBUG');
// Получаем размер файла из S3
$fileSize = getS3FileSize($file['file_url']);
writeLog("📏 Размер файла: " . number_format($fileSize) . " байт", 'DEBUG');
// Определяем название и описание документа
$documentTitle = $file['upload_description'] ?? $file['original_file_name'] ?? $file['file_name'] ?? 'Документ';
$documentContent = sprintf(
'Загружено через n8n. ID: %s, Контакт: %d, Проект: %d, Загружено: %s',
$file['id'] ?? 'N/A',
$contactId,
$projectId,
$file['uploaded_at'] ?? date('Y-m-d H:i:s')
);
// Создаём документ БЕЗ S3 метаданных (webservice не поддерживает их)
$docElement = [
'notes_title' => $documentTitle,
'filename' => $file['original_file_name'] ?? $file['file_name'], // Оригинальное имя файла
'assigned_user_id' => $assignedUserWsId,
'notecontent' => $documentContent,
'filetype' => 'application/pdf',
'filesize' => (string)$fileSize,
'filelocationtype' => 'E', // External URL
'fileversion' => '1.0',
'filestatus' => '1', // Active
'folderid' => $folderWsId,
];
writeLog("📤 Создаём документ '$documentTitle' через webservice", 'INFO');
writeLog("📤 Элемент документа: " . json_encode($docElement, JSON_UNESCAPED_UNICODE), 'DEBUG');
// Отправляем запрос на создание документа
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'operation' => 'create',
'sessionName' => $sessionName,
'elementType' => 'Documents',
'element' => json_encode($docElement, JSON_UNESCAPED_UNICODE),
]);
$resp = curl_exec($ch);
if ($resp === false) {
throw new Exception('CURL error: ' . curl_error($ch));
}
$resp = ltrim($resp, "\xEF\xBB\xBF\x00\x09\x0A\x0D\x20");
writeLog("📥 Ответ создания документа: " . substr($resp, 0, 500), 'DEBUG');
$doc = json_decode($resp, true);
if (!$doc || !$doc['success'] || empty($doc['result']['id'])) {
throw new Exception('Failed to create document: ' . substr($resp, 0, 200));
}
$documentWsId = $doc['result']['id'];
list(, $docNumericId) = explode('x', $documentWsId, 2);
writeLog("✅ Документ создан: $documentWsId (numeric: $docNumericId)", 'SUCCESS');
// ВАЖНО: Обновляем S3 метаданные напрямую в базе данных
$s3UpdateSuccess = updateDocumentS3Metadata($docNumericId, $s3Info, $fileSize, $file['original_file_name'] ?? $file['file_name']);
// Привязываем к проекту
writeLog("🔗 Привязываем документ $documentWsId к проекту $projectWsId", 'INFO');
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'operation' => 'AddRelated',
'sessionName' => $sessionName,
'sourceRecordId' => $projectWsId,
'relatedRecordId' => $documentWsId,
]);
$resp = curl_exec($ch);
$relationOk = false;
if ($resp !== false) {
$resp = ltrim($resp, "\xEF\xBB\xBF\x00\x09\x0A\x0D\x20");
writeLog("📥 Ответ AddRelated: " . substr($resp, 0, 200), 'DEBUG');
$rel = json_decode($resp, true);
$relationOk = isset($rel['result']['message']) && $rel['result']['message'] === 'successfull';
}
// Если webservice не сработал - используем прямую привязку
if (!$relationOk) {
writeLog("⚠️ AddRelated не сработал, используем прямую привязку", 'WARNING');
try {
// Устанавливаем current_user для CRMEntity
if (!isset($current_user) || !$current_user) {
$current_user = new Users();
$current_user->retrieveCurrentUserInfoFromFile($userId);
}
$focus = CRMEntity::getInstance('Project');
relateEntities($focus, 'Project', $projectId, 'Documents', (int)$docNumericId);
writeLog("✅ Прямая привязка успешна", 'SUCCESS');
} catch (Exception $directRelationError) {
writeLog("❌ Прямая привязка не удалась: " . $directRelationError->getMessage(), 'ERROR');
}
} else {
writeLog("✅ AddRelated успешен", 'SUCCESS');
}
// Возвращаем результат с сохранением всех исходных данных
$result = array_merge($file, [
'status' => 'success',
'projectid' => $projectId,
'contactid' => $contactId,
'crm_result' => [
'document_id' => $documentWsId,
'document_numeric_id' => $docNumericId,
'project_id' => $projectId,
'contact_id' => $contactId,
'folder_id' => $folderWsId,
's3_bucket' => $s3Info['bucket'],
's3_key' => $s3Info['key'],
'file_size' => $fileSize,
's3_metadata_updated' => $s3UpdateSuccess,
'message' => 'Документ создан с правильными S3 метаданными и привязан к проекту' . (!$relationOk ? ' (прямая привязка)' : '')
]
]);
$results[] = $result;
writeLog("✅ Файл '$fileName' успешно обработан с S3 метаданными", 'SUCCESS');
} catch (Exception $e) {
writeLog("❌ Ошибка для файла '$fileName': " . $e->getMessage(), 'ERROR');
$results[] = array_merge($file, [
'status' => 'error',
'projectid' => $projectId,
'contactid' => $contactId,
'crm_result' => [
'message' => $e->getMessage()
]
]);
}
}
curl_close($ch);
$successCount = count(array_filter($results, function($r) { return $r['status'] === 'success'; }));
writeLog("🏁 Обработка завершена. Успешно: $successCount/" . count($results), 'INFO');
return $results;
}
// Обработка запроса
if ($IS_POST) {
writeLog('=== START POST REQUEST V2 (DETAILED LOGGING) ===', 'INFO');
writeLog('Headers: ' . json_encode(getallheaders(), JSON_UNESCAPED_UNICODE), 'DEBUG');
// Получаем и проверяем входные данные
$input = file_get_contents('php://input');
writeLog('Raw input: ' . substr($input, 0, 1000) . (strlen($input) > 1000 ? '...(truncated)' : ''), 'DEBUG');
$input = ltrim($input, "\xEF\xBB\xBF\x00\x09\x0A\x0D\x20");
$data = json_decode($input, true);
if (json_last_error() !== JSON_ERROR_NONE) {
writeLog('❌ JSON Error: ' . json_last_error_msg(), 'ERROR');
json_response([
'success' => false,
'error' => ['message' => 'Invalid JSON: ' . json_last_error_msg()]
], 400);
}
writeLog('Parsed data keys: ' . implode(', ', array_keys($data)), 'DEBUG');
// Проверяем обязательные поля
if (empty($data['sessionName'])) {
writeLog('❌ Error: sessionName is required', 'ERROR');
json_response([
'success' => false,
'error' => ['message' => 'sessionName is required in request data']
], 400);
}
if (empty($data['projectid'])) {
writeLog('❌ Error: projectid is required', 'ERROR');
json_response([
'success' => false,
'error' => ['message' => 'projectid is required in request data']
], 400);
}
if (empty($data['contactid'])) {
writeLog('❌ Error: contactid is required', 'ERROR');
json_response([
'success' => false,
'error' => ['message' => 'contactid is required in request data']
], 400);
}
// Поддерживаем оба формата: files и documents
$filesArray = null;
if (!empty($data['files']) && is_array($data['files'])) {
$filesArray = $data['files'];
} elseif (!empty($data['documents']) && is_array($data['documents'])) {
$filesArray = $data['documents'];
}
if (empty($filesArray)) {
writeLog('❌ Error: files or documents array is required', 'ERROR');
json_response([
'success' => false,
'error' => ['message' => 'files or documents array is required in request data']
], 400);
}
writeLog("🔑 Сессия: {$data['sessionName']}", 'INFO');
writeLog("📋 Проект: {$data['projectid']}, Контакт: {$data['contactid']}", 'INFO');
writeLog('📄 Файлов: ' . count($filesArray), 'INFO');
// Нормализуем данные: всегда используем 'files' внутри функции
$normalizedData = $data;
$normalizedData['files'] = $filesArray;
// Создаём документы с правильными S3 метаданными
$results = createDocumentsWithSession($normalizedData);
// Успешный ответ
writeLog('✅ Success: processed ' . count($results) . ' files with S3 metadata', 'SUCCESS');
json_response([
'success' => true,
'total_processed' => count($filesArray),
'results' => $results,
'session_used' => $sessionName
]);
} else {
// GET запрос - документация API
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => true,
'message' => 'Upload Documents API v2 - Detailed Logging Version',
'endpoint' => 'POST to this URL with complete data object',
'logging' => 'Detailed logs available in logs/upload_documents_v2_detailed.log',
'format' => [
'sessionName' => 'string (required - from n8n CRM login)',
'projectid' => 'string (required - project ID)',
'contactid' => 'string (required - contact ID)',
'user_id' => 'string (optional - default "1")',
'files' => [ // или 'documents' - поддерживаются оба формата
[
'id' => 'string (file UUID)',
'file_id' => 'string (S3 path)',
'file_url' => 'string (required - full S3 URL)',
'file_name' => 'string (S3 filename)',
'field_name' => 'string (form field)',
'uploaded_at' => 'string (ISO datetime)',
'original_file_name' => 'string (user filename)',
'upload_description' => 'string (document description)',
'filename_for_upload' => 'string (display filename)'
]
]
],
'changes' => [
'Added detailed logging with levels (INFO, DEBUG, SUCCESS, WARNING, ERROR)',
'Fixed S3 metadata handling via direct database update',
'Proper filelocationtype=E setting',
'Correct filename field usage',
'Added S3 bucket and key extraction',
'Added file size detection from S3',
'Added s3_metadata_updated flag in response'
]
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
?>