403 lines
17 KiB
PHP
Executable File
403 lines
17 KiB
PHP
Executable File
<?php
|
||
// upload_documents_to_crm.php
|
||
|
||
ini_set('display_errors', 1);
|
||
ini_set('display_startup_errors', 1);
|
||
error_reporting(E_ALL);
|
||
// Быстрый ping для проверки доступности скрипта из браузера/HTTP-клиента
|
||
if (isset($_GET['ping'])) {
|
||
header('Content-Type: text/plain; charset=utf-8');
|
||
echo 'pong';
|
||
exit;
|
||
}
|
||
|
||
// Подключение к CRM
|
||
require_once('config.inc.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('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');
|
||
// Инициализируем подключение к БД явно
|
||
$adb = PearDatabase::getInstance();
|
||
|
||
// Управление вербозностью: для POST по умолчанию тихий режим (чистый JSON)
|
||
$__IS_POST__ = (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST');
|
||
$__VERBOSE__ = (!$__IS_POST__) || (isset($_GET['verbose']) && $_GET['verbose'] == '1');
|
||
if ($__IS_POST__) {
|
||
// В API-режиме скрываем любые варнинги/эхо и возвращаем только JSON
|
||
ini_set('display_errors', 0);
|
||
ini_set('display_startup_errors', 0);
|
||
}
|
||
|
||
// Глобальный логгер для этого эндпоинта
|
||
if (!function_exists('writeLog')) {
|
||
function writeLog($message) {
|
||
$logFile = '/logs/upload_documents.log';
|
||
$timestamp = date('Y-m-d H:i:s');
|
||
file_put_contents($logFile, "[$timestamp] $message\n", FILE_APPEND | LOCK_EX);
|
||
}
|
||
}
|
||
|
||
// Унифицированный JSON-ответ и обработка фатальных ошибок для POST
|
||
function json_response($payload, $code = 200) {
|
||
if (!headers_sent()) {
|
||
http_response_code($code);
|
||
header('Content-Type: application/json; charset=utf-8');
|
||
}
|
||
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||
}
|
||
|
||
if ($__IS_POST__) {
|
||
register_shutdown_function(function () {
|
||
$e = error_get_last();
|
||
if ($e && in_array($e['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
|
||
writeLog('FATAL: '.$e['message'].' in '.$e['file'].':'.$e['line']);
|
||
json_response([
|
||
'success' => false,
|
||
'error' => [
|
||
'type' => 'fatal',
|
||
'message' => 'Internal error',
|
||
]
|
||
], 500);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Функция для получения entity ID по имени модуля
|
||
*/
|
||
function vtws_getEntityId($entityName) {
|
||
global $adb;
|
||
$wsrs = $adb->pquery('select id from vtiger_ws_entity where name=?', array($entityName));
|
||
if ($wsrs && $adb->num_rows($wsrs) == 1) {
|
||
$wsid = $adb->query_result($wsrs, 0, 0);
|
||
} else {
|
||
$wsid = 0;
|
||
}
|
||
return $wsid;
|
||
}
|
||
|
||
/**
|
||
* Функция для получения webservice ID пользователя
|
||
*/
|
||
function getWebserviceUserId($userId) {
|
||
global $adb;
|
||
$entityId = vtws_getEntityId('Users');
|
||
return $entityId . 'x' . $userId;
|
||
}
|
||
|
||
/**
|
||
* Функция для получения webservice ID проекта
|
||
*/
|
||
function getWebserviceProjectId($projectId) {
|
||
global $adb;
|
||
$entityId = vtws_getEntityId('Project');
|
||
return $entityId . 'x' . $projectId;
|
||
}
|
||
|
||
/**
|
||
* Префикс WS для папок документов
|
||
*/
|
||
function getDocumentFoldersWsPrefix() {
|
||
global $adb;
|
||
$rs = $adb->pquery("SELECT id FROM vtiger_ws_entity WHERE name=?", ['DocumentFolders']);
|
||
if ($rs && $adb->num_rows($rs) > 0) {
|
||
return (int)$adb->query_result($rs, 0, 'id');
|
||
}
|
||
return 22;
|
||
}
|
||
|
||
/**
|
||
* Получить WS ID папки по имени, иначе null
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* Безопасная аутентификация через challenge/response
|
||
*/
|
||
function authenticateWithCRM($username) {
|
||
// 1) Получаем challenge токен
|
||
$challenge = vtws_getchallenge($username);
|
||
if (empty($challenge['token'])) {
|
||
throw new Exception('Не удалось получить challenge token');
|
||
}
|
||
// 2) Берем accesskey пользователя
|
||
$user = new Users();
|
||
$userId = $user->retrieve_user_id($username);
|
||
if (!$userId) {
|
||
throw new Exception("Пользователь {$username} не найден");
|
||
}
|
||
$accessKey = vtws_getUserAccessKey($userId);
|
||
if (!$accessKey) {
|
||
throw new Exception('Access key пользователя не найден');
|
||
}
|
||
// 3) Логинимся
|
||
$generatedKey = md5($challenge['token'] . $accessKey);
|
||
$loggedUser = vtws_login($username, $generatedKey);
|
||
// Для отладки можно писать в лог
|
||
// error_log("WS login ok for $username, token={$challenge['token']} md5=$generatedKey");
|
||
// Установим $current_user для операций CRMEntity
|
||
global $current_user;
|
||
$current_user = new Users();
|
||
$current_user->retrieveCurrentUserInfoFromFile((int)$userId);
|
||
return $loggedUser;
|
||
}
|
||
|
||
/**
|
||
* Функция для получения ID папки по умолчанию
|
||
*/
|
||
function getDefaultFolderId() {
|
||
global $adb;
|
||
$result = $adb->pquery('SELECT folderid FROM vtiger_attachmentsfolder WHERE foldername = ? LIMIT 1', array('Default'));
|
||
if ($result && $adb->num_rows($result) > 0) {
|
||
$folderId = $adb->query_result($result, 0, 'folderid');
|
||
$entityId = vtws_getEntityId('DocumentFolders');
|
||
return $entityId . 'x' . $folderId;
|
||
}
|
||
return '22x1'; // Fallback ID
|
||
}
|
||
|
||
/**
|
||
* Основная функция для создания документов в CRM
|
||
*/
|
||
function createDocumentsInCRM($filesArray, $adminUsername = 'api') {
|
||
global $adb;
|
||
|
||
|
||
writeLog("🚀 Начинаем создание документов для пользователя: $adminUsername");
|
||
|
||
try {
|
||
writeLog("🔐 Попытка аутентификации...");
|
||
|
||
// Проверяем существование пользователя
|
||
$user = new Users();
|
||
$userId = $user->retrieve_user_id($adminUsername);
|
||
|
||
if (!$userId) {
|
||
throw new Exception("Пользователь '$adminUsername' не найден");
|
||
}
|
||
|
||
writeLog("✅ Пользователь найден, ID: $userId");
|
||
|
||
// Безопасная аутентификация
|
||
$authenticatedUser = authenticateWithCRM($adminUsername);
|
||
writeLog("✅ Аутентификация успешна");
|
||
|
||
$results = [];
|
||
|
||
foreach ($filesArray as $index => $fileData) {
|
||
writeLog("📄 Обрабатываем файл #$index: " . ($fileData['file_name'] ?? 'Unknown'));
|
||
|
||
try {
|
||
// Получаем webservice ID пользователя и проекта
|
||
$assignedUserId = getWebserviceUserId($fileData['user_id']);
|
||
$projectId = getWebserviceProjectId($fileData['projectid']);
|
||
// Папка: сначала пробуем 'Суд', иначе Default
|
||
$folderId = getFolderWsIdByName('Суд');
|
||
if (!$folderId) {
|
||
$folderId = getDefaultFolderId();
|
||
writeLog("⚠️ Папка 'Суд' не найдена, используем по умолчанию: $folderId");
|
||
} else {
|
||
writeLog("📁 Используем папку 'Суд': $folderId");
|
||
}
|
||
|
||
writeLog("🔗 WebService IDs - User: $assignedUserId, Project: $projectId, Folder: $folderId");
|
||
|
||
// Формируем данные документа
|
||
$documentData = [
|
||
'notes_title' => $fileData['description'] ?: $fileData['file_name'],
|
||
'filename' => $fileData['url'],
|
||
'assigned_user_id' => $assignedUserId,
|
||
'notecontent' => 'Автоматически загружен из S3. Контакт: ' . $fileData['contactid'],
|
||
'filetype' => 'application/pdf',
|
||
'filesize' => '0',
|
||
'filelocationtype' => 'E',
|
||
'fileversion' => '1.0',
|
||
'filestatus' => '1',
|
||
'filedownloadcount' => '0',
|
||
'folderid' => $folderId,
|
||
'created_user_id' => $assignedUserId,
|
||
'starred' => '0',
|
||
'tags' => 'S3,автозагрузка'
|
||
];
|
||
|
||
writeLog("📋 Данные документа подготовлены: " . json_encode($documentData, JSON_UNESCAPED_UNICODE));
|
||
|
||
// Создаем документ
|
||
$document = vtws_create('Documents', $documentData, $authenticatedUser);
|
||
|
||
if ($document && isset($document['id'])) {
|
||
writeLog("✅ Документ создан с ID: " . $document['id']);
|
||
|
||
// Привязываем документ к проекту
|
||
try {
|
||
vtws_add_related($projectId, $document['id'], false, $authenticatedUser);
|
||
writeLog("✅ Документ привязан к проекту");
|
||
|
||
$results[] = [
|
||
'status' => 'success',
|
||
'file_name' => $fileData['file_name'],
|
||
'document_id' => $document['id'],
|
||
'project_id' => $fileData['projectid'],
|
||
'url' => $fileData['url'],
|
||
'message' => 'Документ успешно создан и привязан к проекту'
|
||
];
|
||
|
||
} catch (Exception $e) {
|
||
// Fallback: привязываем напрямую через CRMEntity
|
||
writeLog("⚠️ Webservice AddRelated не сработал: " . $e->getMessage() . ". Пытаемся привязать напрямую...");
|
||
require_once 'data/CRMEntity.php';
|
||
require_once 'modules/Vtiger/CRMEntity.php';
|
||
$docIds = vtws_getIdComponents($document['id']);
|
||
$docNumericId = (int)$docIds[1];
|
||
$focus = CRMEntity::getInstance('Project');
|
||
relateEntities($focus, 'Project', (int)$fileData['projectid'], 'Documents', $docNumericId);
|
||
writeLog("✅ Привязка выполнена напрямую (Project {$fileData['projectid']} -> Document {$docNumericId})");
|
||
$results[] = [
|
||
'status' => 'success',
|
||
'file_name' => $fileData['file_name'],
|
||
'document_id' => $document['id'],
|
||
'project_id' => $fileData['projectid'],
|
||
'url' => $fileData['url'],
|
||
'message' => 'Документ создан и привязан к проекту (fallback)'
|
||
];
|
||
}
|
||
} else {
|
||
writeLog("❌ Ошибка создания документа");
|
||
$results[] = [
|
||
'status' => 'error',
|
||
'file_name' => $fileData['file_name'],
|
||
'project_id' => $fileData['projectid'],
|
||
'url' => $fileData['url'],
|
||
'message' => 'Ошибка создания документа'
|
||
];
|
||
}
|
||
|
||
} catch (Exception $e) {
|
||
writeLog("❌ Критическая ошибка для файла: " . $e->getMessage());
|
||
|
||
$results[] = [
|
||
'status' => 'error',
|
||
'file_name' => $fileData['file_name'] ?? 'Unknown',
|
||
'project_id' => $fileData['projectid'] ?? 'Unknown',
|
||
'url' => $fileData['url'] ?? 'Unknown',
|
||
'message' => 'Ошибка: ' . $e->getMessage()
|
||
];
|
||
}
|
||
}
|
||
|
||
writeLog("🏁 Обработка завершена. Всего файлов: " . count($filesArray));
|
||
return $results;
|
||
|
||
} catch (Exception $e) {
|
||
writeLog("💥 Критическая ошибка: " . $e->getMessage());
|
||
return [
|
||
'status' => 'critical_error',
|
||
'message' => 'Ошибка аутентификации в CRM: ' . $e->getMessage()
|
||
];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получение access key пользователя
|
||
*/
|
||
function vtws_getUserAccessKey($userId) {
|
||
global $adb;
|
||
$sql = "SELECT accesskey FROM vtiger_users WHERE id = ?";
|
||
$result = $adb->pquery($sql, array($userId));
|
||
|
||
if ($result && $adb->num_rows($result) > 0) {
|
||
return $adb->query_result($result, 0, 'accesskey');
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// Пример использования:
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
// Получаем JSON данные (без BOM) и пробуем распознать формат
|
||
writeLog('POST '.$_SERVER['REQUEST_URI'].' CT='.(isset($_SERVER['CONTENT_TYPE'])?$_SERVER['CONTENT_TYPE']:'').', CL='.(isset($_SERVER['CONTENT_LENGTH'])?$_SERVER['CONTENT_LENGTH']:'').' UA='.(isset($_SERVER['HTTP_USER_AGENT'])?$_SERVER['HTTP_USER_AGENT']:'').', IP='.(isset($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:'') );
|
||
$input = file_get_contents('php://input');
|
||
writeLog('RAW BODY: '.substr($input,0,2048).(strlen($input)>2048?'...<truncated>':''));
|
||
$input = ltrim($input, "\xEF\xBB\xBF\x00\x09\x0A\x0D\x20");
|
||
$filesArray = json_decode($input, true);
|
||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||
writeLog('JSON ERROR: '.json_last_error_msg());
|
||
json_response(['success' => false, 'error' => ['message' => 'Invalid JSON: '.json_last_error_msg()]], 400);
|
||
exit;
|
||
}
|
||
// Авто-распаковка n8n-форматов:
|
||
// 1) [{"data":[...]}]
|
||
if (is_array($filesArray) && count($filesArray) === 1 && isset($filesArray[0]['data']) && is_array($filesArray[0]['data'])) {
|
||
$filesArray = $filesArray[0]['data'];
|
||
}
|
||
// 2) {"data":[...]}
|
||
if (is_array($filesArray) && isset($filesArray['data']) && is_array($filesArray['data'])) {
|
||
$filesArray = $filesArray['data'];
|
||
}
|
||
if (!is_array($filesArray)) {
|
||
writeLog('BAD FORMAT: not an array after normalization');
|
||
json_response(['success' => false, 'error' => ['message' => 'JSON must be an array of file items']], 400);
|
||
exit;
|
||
}
|
||
writeLog('ITEMS: '.count($filesArray));
|
||
|
||
// Создаем документы
|
||
$results = createDocumentsInCRM($filesArray);
|
||
writeLog('DONE: processed='.count($filesArray));
|
||
|
||
// Возвращаем результат
|
||
json_response([
|
||
'success' => true,
|
||
'results' => $results,
|
||
'total_processed' => count($filesArray)
|
||
]);
|
||
|
||
} else {
|
||
// Тестовый запуск с вашими данными
|
||
$testData = [
|
||
[
|
||
"session_token" => "sess_dedc540e-f60d-491b-b6bb-ef7f7d04a366",
|
||
"user_id" => 1,
|
||
"contactid" => 120374,
|
||
"description" => "Иск",
|
||
"projectid" => 354918,
|
||
"pages" => 2,
|
||
"url" => "https://s3.twcstorage.ru/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/clientright/120374/1757346451126.pdf",
|
||
"file_name" => "1757346451126.pdf"
|
||
],
|
||
[
|
||
"session_token" => "sess_dedc540e-f60d-491b-b6bb-ef7f7d04a366",
|
||
"user_id" => 1,
|
||
"contactid" => 120374,
|
||
"description" => "доказательство направления претензии",
|
||
"projectid" => 354918,
|
||
"pages" => 4,
|
||
"url" => "https://s3.twcstorage.ru/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/clientright/120374/1757346454717.pdf",
|
||
"file_name" => "1757346454717.pdf"
|
||
]
|
||
];
|
||
|
||
$results = createDocumentsInCRM($testData);
|
||
header('Content-Type: application/json');
|
||
echo json_encode([
|
||
'success' => true,
|
||
'results' => $results,
|
||
'total_processed' => count($testData)
|
||
], JSON_UNESCAPED_UNICODE);
|
||
}
|
||
?>
|