Files
crm.clientright.ru/crm_extensions/file_storage/api/create_from_template.php
Fedor cd90b0d58a feat: Добавлен инструмент генерации документов для AI Ассистента
- Создан API create_document_with_text.php для создания DOCX/XLSX/PPTX с текстом от AI
- Поддержка Markdown форматирования (заголовки, жирный, курсив, списки, код)
- Установлен PHPWord для красивого форматирования документов
- Исправлены пути сохранения (crm2/CRM_Active_Files/... без /crm/ в начале)
- Замена пробелов на подчеркивания в именах папок
- Создана документация для AI и разработчиков
- Добавлены API для работы с шаблонами Nextcloud
2025-11-12 19:46:06 +03:00

252 lines
8.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
/**
* Создание документа из шаблона Nextcloud
*
* Алгоритм:
* 1. Получаем шаблон из Nextcloud через WebDAV
* 2. Заполняем переменные через PHPWord
* 3. Сохраняем готовый документ в папку проекта
* 4. Открываем в OnlyOffice
*/
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Параметры
$module = $_GET['module'] ?? '';
$recordId = $_GET['recordId'] ?? '';
$recordName = $_GET['recordName'] ?? '';
$fileName = $_GET['fileName'] ?? '';
$templateName = $_GET['templateName'] ?? ''; // Имя шаблона (например, "pretenziya.docx")
$variables = json_decode($_GET['variables'] ?? '{}', true); // Переменные для заполнения
if (empty($module) || empty($recordId) || empty($fileName) || empty($templateName)) {
die(json_encode(['success' => false, 'error' => 'Не указаны обязательные параметры']));
}
// Nextcloud credentials
$nextcloudUrl = 'https://office.clientright.ru:8443';
$username = 'admin';
$password = 'office';
// Определяем папку модуля
$moduleFolders = [
'Project' => 'Project',
'Contacts' => 'Contacts',
'Accounts' => 'Accounts',
'Invoice' => 'Invoice',
'Quotes' => 'Quotes',
'SalesOrder' => 'SalesOrder',
'PurchaseOrder' => 'PurchaseOrder',
'HelpDesk' => 'HelpDesk',
'Leads' => 'Leads',
'Potentials' => 'Potentials'
];
$moduleFolder = $moduleFolders[$module] ?? 'Other';
// Формируем имя папки записи
$recordName = preg_replace('/[\/\\\\:\*\?"<>\|]/', '_', $recordName);
$folderName = $recordName . '_' . $recordId;
// ONLYOFFICE хранит шаблоны в папке /Templates/ в корне пользователя
// Путь к шаблону в Nextcloud
$templatePath = "/Templates/{$templateName}";
$templateWebDAVUrl = $nextcloudUrl . '/remote.php/dav/files/' . $username . $templatePath;
// Путь к готовому документу
$fileType = pathinfo($templateName, PATHINFO_EXTENSION);
$ncPath = "/crm/crm2/CRM_Active_Files/Documents/{$moduleFolder}/{$folderName}/{$fileName}.{$fileType}";
error_log("=== CREATE FROM TEMPLATE ===");
error_log("Template: {$templateName}");
error_log("Variables: " . json_encode($variables, JSON_UNESCAPED_UNICODE));
error_log("Output path: {$ncPath}");
// 1. СКАЧИВАЕМ ШАБЛОН ИЗ NEXTCLOUD
$ch = curl_init($templateWebDAVUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$templateContent = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200 || empty($templateContent)) {
die(json_encode(['success' => false, 'error' => "Шаблон не найден: {$templateName}"]));
}
error_log("✅ Template downloaded (" . strlen($templateContent) . " bytes)");
// 2. ЗАПОЛНЯЕМ ПЕРЕМЕННЫЕ В ШАБЛОНЕ
$filledContent = fillTemplateVariables($templateContent, $variables, $fileType);
// 3. СОХРАНЯЕМ В S3
$s3Path = ltrim($ncPath, '/');
$s3Client = new Aws\S3\S3Client([
'version' => 'latest',
'region' => 'ru-1',
'endpoint' => 'https://s3.twcstorage.ru',
'use_path_style_endpoint' => true,
'credentials' => [
'key' => EnvLoader::getRequired('S3_ACCESS_KEY'),
'secret' => EnvLoader::getRequired('S3_SECRET_KEY')
],
'suppress_php_deprecation_warning' => true
]);
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
try {
$result = $s3Client->putObject([
'Bucket' => $bucket,
'Key' => $s3Path,
'Body' => $filledContent,
'ContentType' => getContentType($fileType)
]);
error_log("✅ File saved to S3: {$s3Path}");
} catch (Exception $e) {
error_log("Failed to save to S3: " . $e->getMessage());
die(json_encode(['success' => false, 'error' => "Ошибка сохранения: " . $e->getMessage()]));
}
// 4. ПУБЛИКУЕМ СОБЫТИЕ В REDIS
try {
$redis = new Predis\Client([
'scheme' => 'tcp',
'host' => 'crm.clientright.ru',
'port' => 6379,
'password' => 'CRM_Redis_Pass_2025_Secure!'
]);
$event = json_encode([
'type' => 'file_created',
'source' => 'crm_template',
'path' => $s3Path,
'timestamp' => time()
]);
$redis->publish('crm:file:events', $event);
error_log("✅ Published event to Redis");
} catch (Exception $e) {
error_log("Redis publish failed: " . $e->getMessage());
}
// 5. ОТКРЫВАЕМ В ONLYOFFICE
$s3Url = 'https://s3.twcstorage.ru/' . $bucket . '/' . $s3Path;
$redirectUrl = '/crm_extensions/file_storage/api/open_file_v2.php?recordId=' . urlencode($recordId) . '&fileName=' . urlencode($s3Url);
header('Location: ' . $redirectUrl);
exit;
/**
* Заполняет переменные в шаблоне
*
* Поддерживает два формата:
* 1. Простая замена {VARIABLE_NAME} → значение
* 2. PHPWord для сложных документов
*/
function fillTemplateVariables($content, $variables, $fileType) {
if ($fileType === 'docx') {
// Используем PHPWord для DOCX
return fillDocxTemplate($content, $variables);
} else {
// Для других форматов - простая замена
return fillSimpleTemplate($content, $variables);
}
}
/**
* Заполнение DOCX через PHPWord
*/
function fillDocxTemplate($content, $variables) {
// Сохраняем во временный файл
$tempFile = tempnam(sys_get_temp_dir(), 'template_') . '.docx';
file_put_contents($tempFile, $content);
try {
$phpWord = \PhpOffice\PhpWord\IOFactory::load($tempFile);
// Заменяем переменные во всех секциях
foreach ($phpWord->getSections() as $section) {
foreach ($section->getElements() as $element) {
if ($element instanceof \PhpOffice\PhpWord\Element\Text) {
$text = $element->getText();
$text = replaceVariables($text, $variables);
$element->setText($text);
} elseif ($element instanceof \PhpOffice\PhpWord\Element\TextRun) {
foreach ($element->getElements() as $textElement) {
if ($textElement instanceof \PhpOffice\PhpWord\Element\Text) {
$text = $textElement->getText();
$text = replaceVariables($text, $variables);
$textElement->setText($text);
}
}
}
}
}
// Сохраняем результат
$writer = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
$outputFile = tempnam(sys_get_temp_dir(), 'output_') . '.docx';
$writer->save($outputFile);
$result = file_get_contents($outputFile);
// Удаляем временные файлы
unlink($tempFile);
unlink($outputFile);
return $result;
} catch (Exception $e) {
error_log("PHPWord error: " . $e->getMessage());
// Fallback на простую замену
unlink($tempFile);
return fillSimpleTemplate($content, $variables);
}
}
/**
* Простая замена переменных {VAR} → значение
*/
function fillSimpleTemplate($content, $variables) {
foreach ($variables as $key => $value) {
$content = str_replace('{' . strtoupper($key) . '}', $value, $content);
$content = str_replace('{{' . strtoupper($key) . '}}', $value, $content);
}
return $content;
}
/**
* Универсальная замена переменных
*/
function replaceVariables($text, $variables) {
foreach ($variables as $key => $value) {
$text = str_replace('{' . strtoupper($key) . '}', $value, $text);
$text = str_replace('{{' . strtoupper($key) . '}}', $value, $text);
}
return $text;
}
/**
* Определяет Content-Type для файла
*/
function getContentType($fileType) {
$types = [
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
];
return $types[$fileType] ?? 'application/octet-stream';
}