- Создан API create_document_with_text.php для создания DOCX/XLSX/PPTX с текстом от AI - Поддержка Markdown форматирования (заголовки, жирный, курсив, списки, код) - Установлен PHPWord для красивого форматирования документов - Исправлены пути сохранения (crm2/CRM_Active_Files/... без /crm/ в начале) - Замена пробелов на подчеркивания в именах папок - Создана документация для AI и разработчиков - Добавлены API для работы с шаблонами Nextcloud
252 lines
8.9 KiB
PHP
252 lines
8.9 KiB
PHP
<?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';
|
||
}
|
||
|