get('record'); $module = $request->get('source_module') ?: getSalesEntityType($record); if (!isPermitted($module, 'DetailView', $record)) { throw new AppException('LBL_PERMISSION_DENIED'); } } public function process(Vtiger_Request $request) { $recordId = (int)$request->get('record'); $templateId = (int)$request->get('template_id'); $format = strtolower($request->get('format') ?: 'pdf'); // pdf | docx $mode = strtolower($request->get('mode') ?: 'download'); // download | save_to_documents $module = $request->get('source_module') ?: getSalesEntityType($recordId); if (!$recordId || !$templateId) { echo json_encode(['success' => false, 'error' => 'Missing record or template_id']); return; } require_once dirname(__DIR__) . '/models/OnlyOfficeTemplates_Model.php'; require_once dirname(__DIR__) . '/resources/S3Helper.php'; require_once dirname(__DIR__) . '/resources/MergeService.php'; require_once dirname(__DIR__) . '/resources/ConvertService.php'; $model = new OnlyOfficeTemplates_Model(); $template = $model->getTemplateById($templateId); if (!$template) { echo json_encode(['success' => false, 'error' => 'Template not found or access denied']); return; } $config = $model->getConfig(); $s3 = new OnlyOfficeTemplates_S3Helper($config); $mergeService = new OnlyOfficeTemplates_MergeService($s3, $config); $convertService = new OnlyOfficeTemplates_ConvertService($config); $placeholders = $mergeService->buildPlaceholders($module, $recordId); $tempDir = null; $docxPath = null; $pdfPath = null; try { $docxPath = $mergeService->mergeToFile($template['s3_key'], $placeholders); $tempDir = dirname($docxPath); $baseName = pathinfo($template['file_name'], PATHINFO_FILENAME); $outExt = ($format === 'pdf') ? 'pdf' : 'docx'; $outFileName = $baseName . '_' . $recordId . '.' . $outExt; if ($format === 'pdf') { $docxUrl = $this->putMergedDocxForConversion($s3, $config, $docxPath, $recordId, $templateId); if (!$docxUrl) { echo json_encode(['success' => false, 'error' => 'Could not expose DOCX URL for conversion']); return; } $result = $convertService->convertToPdf($docxUrl, $template['file_name']); if (!$result['success']) { echo json_encode(['success' => false, 'error' => $result['error']]); return; } $pdfPath = $result['pdfPath']; } if ($mode === 'save_to_documents') { $fileToSave = ($format === 'pdf') ? $pdfPath : $docxPath; $docId = $this->saveToDocuments($request, $module, $recordId, $fileToSave, $outFileName, $config); $this->cleanupTemp($tempDir, $docxPath, $pdfPath); echo json_encode(['success' => true, 'document_id' => $docId, 'message' => 'Saved to Documents']); return; } $downloadPath = ($format === 'pdf') ? $pdfPath : $docxPath; $mime = ($format === 'pdf') ? 'application/pdf' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; header('Content-Type: ' . $mime); header('Content-Disposition: attachment; filename="' . basename($outFileName) . '"'); header('Content-Length: ' . filesize($downloadPath)); readfile($downloadPath); $this->cleanupTemp($tempDir, $docxPath, $pdfPath); } catch (Exception $e) { $this->cleanupTemp($tempDir, $docxPath, $pdfPath); if ($request->get('ajax')) { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } else { throw $e; } } } /** * Upload merged DOCX to S3 temp and return public URL for OnlyOffice converter. */ private function putMergedDocxForConversion(OnlyOfficeTemplates_S3Helper $s3, array $config, $localPath, $recordId, $templateId) { $key = $s3->getTempKey($recordId, $templateId, 'docx'); $s3->uploadFile($localPath, $key, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'); $bucket = $s3->getBucket(); $endpoint = $config['s3']['endpoint'] ?? ''; $base = preg_replace('#^https?://#', 'https://', $endpoint); if (empty($base)) { $base = 'https://s3.twcstorage.ru'; } return rtrim($base, '/') . '/' . $bucket . '/' . $key; } /** * Save file as Document record and link to parent. Use Documents S3 structure if FilePathManager available. */ private function saveToDocuments(Vtiger_Request $request, $module, $recordId, $localPath, $fileName, array $config) { $adb = PearDatabase::getInstance(); $currentUser = Users_Record_Model::getCurrentUserModel(); $ownerId = $currentUser->getId(); $docPrefix = $config['documents_s3_prefix'] ?? 'crm2/CRM_Active_Files/Documents'; $bucket = $config['s3_bucket'] ?? $config['s3']['bucket']; $notesId = $adb->getUniqueID('vtiger_crmentity'); if (!$notesId) { $r = $adb->pquery("SELECT MAX(crmid) AS m FROM vtiger_crmentity", []); $notesId = (int)$adb->query_result($r, 0, 'm') + 1; } $title = pathinfo($fileName, PATHINFO_FILENAME); $now = date('Y-m-d H:i:s'); $s3Key = $docPrefix . '/' . $module . '/' . $module . '_' . $recordId . '/' . $title . '_' . $notesId . '.' . pathinfo($fileName, PATHINFO_EXTENSION); $s3 = new OnlyOfficeTemplates_S3Helper($config); $contentType = (pathinfo($fileName, PATHINFO_EXTENSION) === 'pdf') ? 'application/pdf' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; $s3->uploadFile($localPath, $s3Key, $contentType); $fileUrl = 'https://' . ($config['s3']['endpoint'] ?? 's3.twcstorage.ru'); $fileUrl = preg_replace('#^https?://#', '', $fileUrl); $fileUrl = 'https://' . $fileUrl . '/' . $bucket . '/' . $s3Key; $fileSize = filesize($localPath); $adb->pquery( "INSERT INTO vtiger_crmentity (crmid, smownerid, smcreatorid, modifiedby, setype, description, createdtime, modifiedtime, presence, deleted) VALUES (?,?,?,?,?,?,?,?,?,?)", [$notesId, $ownerId, $ownerId, $ownerId, 'Documents', '', $now, $now, 1, 0] ); $adb->pquery( "INSERT INTO vtiger_notes (notesid, title, filename, filesize, filetype, filelocationtype, filedownloadcount, createdtime, modifiedtime, folderid, notecontent) VALUES (?,?,?,?,?,?,?,?,?,?,?)", [$notesId, $title, $fileUrl, $fileSize, $contentType, 'E', 0, $now, $now, 0, ''] ); if (method_exists($adb, 'pquery')) { $adb->pquery("INSERT INTO vtiger_senotesrel (crmid, notesid) VALUES (?,?)", [$recordId, $notesId]); } $adb->pquery("INSERT INTO vtiger_notescf (notesid) VALUES (?)", [$notesId]); if ($this->hasS3Columns($adb)) { $adb->pquery("UPDATE vtiger_notes SET s3_bucket = ?, s3_key = ? WHERE notesid = ?", [$bucket, $s3Key, $notesId]); } return $notesId; } private function hasS3Columns($adb) { static $has = null; if ($has === null) { $r = @$adb->pquery("SHOW COLUMNS FROM vtiger_notes LIKE 's3_key'", []); $has = $r && $adb->num_rows($r) > 0; } return $has; } private function cleanupTemp($tempDir, $docxPath, $pdfPath) { if ($pdfPath && is_file($pdfPath)) { @unlink($pdfPath); } if ($docxPath && is_file($docxPath)) { @unlink($docxPath); } if ($tempDir && is_dir($tempDir)) { @rmdir($tempDir); } } }