feat: OnlyOffice Standalone integration with S3 direct URLs

 ЧТО СДЕЛАНО:
- Поднят новый standalone OnlyOffice Document Server (порт 8083)
- Настроен Nginx для доступа через office.clientright.ru:9443
- Создан open_file_v3_standalone.php для работы с новым OnlyOffice
- Реализована поддержка прямых S3 URL (bucket публичный)
- Добавлен s3_proxy.php с поддержкой Range requests
- Создан onlyoffice_callback.php для сохранения (базовая версия)
- Файлы успешно открываются и загружаются!

⚠️ TODO (на завтра):
- Доработать onlyoffice_callback.php для сохранения обратно в ОРИГИНАЛЬНЫЙ путь в S3
- Добавить Redis маппинг documentKey → S3 path
- Обновить CRM JS для использования open_file_v3_standalone.php
- Протестировать сохранение файлов
- Удалить тестовые файлы

📊 РЕЗУЛЬТАТ:
- OnlyOffice Standalone РАБОТАЕТ! 
- Файлы открываются напрямую из S3 
- Редактор загружается БЫСТРО 
- Автосохранение настроено  (но нужна доработка callback)
This commit is contained in:
Fedor
2025-11-01 01:02:03 +03:00
parent d7941ac862
commit 269c7ea216
4383 changed files with 282112 additions and 7731 deletions

View File

@@ -1,110 +1,237 @@
<?php
/**
* Простой редирект на файл в Nextcloud БЕЗ CSRF проверок
* Использует FilePathManager для новой структуры файлов
* ФИНАЛ: OnlyOffice + Pre-signed S3 URL
* Теперь с CORS и правильными настройками!
*/
// Включаем отображение ошибок
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/shared/EnvLoader.php';
EnvLoader::load('/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/.env');
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Подключаем конфигурацию и FilePathManager
require_once '/var/www/fastuser/data/www/crm.clientright.ru/config.inc.php';
require_once '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/FilePathManager.php';
// Получаем параметры
$fileName = isset($_GET['fileName']) ? $_GET['fileName'] : '';
$recordId = isset($_GET['recordId']) ? $_GET['recordId'] : '';
// Если fileName содержит полный URL S3, извлекаем путь к файлу
$ncPath = '';
if (empty($fileName)) {
die("❌ fileName не указан");
}
// Извлекаем S3 путь
$s3Path = '';
if (strpos($fileName, 'http') === 0) {
// Декодируем URL
$fileName = urldecode($fileName);
// Извлекаем путь после bucket ID
// Формат: https://s3.twcstorage.ru/BUCKET_ID/crm2/CRM_Active_Files/...
$bucketId = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
$pos = strpos($fileName, $bucketId . '/');
if ($pos !== false) {
$s3Path = substr($fileName, $pos + strlen($bucketId) + 1);
// Nextcloud путь = /crm/ + s3_path
$ncPath = '/crm/' . $s3Path;
}
}
if (empty($ncPath)) {
die(" Ошибка: Не удалось извлечь путь из URL: $fileName");
if (empty($s3Path)) {
die("Не удалось извлечь путь из URL");
}
// Настройки Nextcloud
$nextcloudUrl = 'https://office.clientright.ru:8443';
$username = 'admin';
$password = 'office';
// Извлекаем расширение файла
$ext = strtolower(pathinfo($s3Path, PATHINFO_EXTENSION));
// Вспомогательная функция: кодирование пути по сегментам (WebDAV)
$encodePath = function(array $segments) {
return implode('/', array_map('rawurlencode', $segments));
};
// ПРЯМОЙ S3 URL (bucket публичный, CORS настроен!)
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
$s3Url = 'https://s3.twcstorage.ru/' . $bucket . '/' . $s3Path;
// Получаем fileId через WebDAV PROPFIND
$fileId = null;
$propfindUrl = $nextcloudUrl . '/remote.php/dav/files/' . $username . $ncPath;
// Генерируем версию и ключ документа
$version = time();
// СЛУЧАЙНЫЙ ключ при каждом запросе, чтобы OnlyOffice не использовал кеш!
$documentKey = md5($s3Path . '_' . $version);
error_log("Nextcloud Editor: PROPFIND -> {$propfindUrl}");
// ПРЯМОЙ S3 URL (bucket публичный, поэтому pre-signed URL не нужен!)
// Bucket поддерживает Range requests и CORS из коробки
$fileUrl = $s3Url;
// XML запрос для получения fileid
$xmlRequest = '<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<oc:fileid/>
</d:prop>
</d:propfind>';
// ОТЛАДКА: Логируем все параметры
error_log("=== OPEN FILE DEBUG ===");
error_log("S3 Path: " . $s3Path);
error_log("File URL: " . $fileUrl);
error_log("File extension: " . $ext);
error_log("Document Key (unique): " . $documentKey);
error_log("Version: " . $version);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $propfindUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, $username . ':' . $password);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PROPFIND');
curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlRequest);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Depth: 0',
'Content-Type: application/xml'
]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
$fileBasename = basename($s3Path);
$fileType = getFileType($ext);
$officeFormats = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
if ($response === false) {
error_log("Nextcloud Editor: Ошибка cURL: " . $curlError);
} else {
error_log("Nextcloud Editor: HTTP код: {$httpCode}");
if ($httpCode === 207 && preg_match('/<oc:fileid>(\d+)<\/oc:fileid>/', $response, $matches)) {
$fileId = (int)$matches[1];
error_log("Nextcloud Editor: Получен fileId: {$fileId}");
} else {
error_log("Nextcloud Editor: Файл не найден по пути: {$ncPath} (HTTP {$httpCode})");
}
if (!in_array($ext, $officeFormats)) {
header('Location: ' . $s3Url);
exit;
}
if (!$fileId) {
$errorMsg = "❌ Ошибка: Не удалось получить fileId для файла {$fileName}";
error_log("Nextcloud Editor ERROR: " . $errorMsg);
die($errorMsg);
}
// Формируем URL для Nextcloud
// РАБОЧИЙ ФОРМАТ - редирект на файл с автооткрытием редактора!
$redirectUrl = $nextcloudUrl . '/apps/files/files/' . $fileId . '?dir=/&editing=true&openfile=true';
// Логирование
error_log("Nextcloud Editor: Redirect to $redirectUrl for file (ID: $fileId)");
// Делаем редирект
header('Location: ' . $redirectUrl);
exit;
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title><?php echo htmlspecialchars($fileBasename); ?></title>
<script src="https://office.clientright.ru:9443/web-apps/apps/api/documents/api.js?v=<?php echo time(); ?>"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
}
#editor {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="editor"></div>
<script>
// Отладка в консоль
console.log('📁 Файл:', <?php echo json_encode($fileBasename); ?>);
console.log('🔗 S3 URL:', <?php echo json_encode($fileUrl); ?>);
console.log('🔑 Document Key (unique):', <?php echo json_encode($documentKey); ?>);
console.log('✅ Standalone OnlyOffice (9443) + Direct S3 URL!');
new DocsAPI.DocEditor("editor", {
"documentType": "<?php echo $fileType; ?>",
"document": {
"fileType": "<?php echo $ext; ?>",
"key": "<?php echo $documentKey; ?>",
"title": <?php echo json_encode($fileBasename); ?>,
"url": <?php echo json_encode($fileUrl); ?>,
"permissions": {
"comment": true,
"download": true,
"edit": true,
"print": true,
"review": true
}
},
"editorConfig": {
"mode": "edit",
"lang": "ru",
"callbackUrl": "https://crm.clientright.ru/crm_extensions/file_storage/api/onlyoffice_callback.php",
"user": {
"id": "user_<?php echo $recordId ?? 'guest'; ?>",
"name": "CRM User"
},
"customization": {
"autosave": true,
"chat": false,
"comments": true,
"compactHeader": false,
"compactToolbar": false,
"help": true,
"hideRightMenu": false,
"logo": {
"image": "https://crm.clientright.ru/layouts/v7/skins/images/logo.png",
"imageEmbedded": "https://crm.clientright.ru/layouts/v7/skins/images/logo.png"
},
"zoom": 100
}
},
"height": "100%",
"width": "100%",
"type": "desktop",
"events": {
"onReady": function() {
console.log('✅ Editor ready!');
},
"onDocumentReady": function() {
console.log('✅ Document loaded!');
},
"onError": function(event) {
console.error('❌ OnlyOffice Error FULL:', JSON.stringify(event, null, 2));
console.error('Event data:', event.data);
console.error('Error code:', event.data.errorCode);
console.error('Error description:', event.data.errorDescription);
// Тестируем доступность URL из браузера
console.log('🧪 Testing S3 URL from browser...');
fetch(<?php echo json_encode($fileUrl); ?>, { method: 'HEAD' })
.then(response => {
console.log('✅ Browser can access S3:', response.status);
})
.catch(error => {
console.error('❌ Browser CANNOT access S3:', error);
});
alert('Ошибка загрузки документа:\n\n' +
'Code: ' + event.data.errorCode + '\n' +
'Description: ' + event.data.errorDescription + '\n\n' +
'Используется Pre-signed URL из S3\n\n' +
'Смотри консоль браузера (F12) для деталей!');
},
"onWarning": function(event) {
console.warn('⚠️ OnlyOffice Warning:', event);
}
}
});
</script>
</body>
</html>
<?php
function getFileType($ext) {
if (in_array($ext, ['doc', 'docx'])) return 'word';
if (in_array($ext, ['xls', 'xlsx'])) return 'cell';
if (in_array($ext, ['ppt', 'pptx'])) return 'slide';
return 'word';
}
function generatePresignedUrl($s3Key, $expirationSeconds) {
try {
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
$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';
// КЛЮЧ: Минимальные параметры = правильная подпись!
$cmd = $s3Client->getCommand('GetObject', [
'Bucket' => $bucket,
'Key' => $s3Key
]);
$request = $s3Client->createPresignedRequest($cmd, "+{$expirationSeconds} seconds");
return (string)$request->getUri();
} catch (Exception $e) {
error_log("Pre-signed URL error: " . $e->getMessage());
return null;
}
}
function getContentType($filename) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$types = [
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls' => 'application/vnd.ms-excel',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'ppt' => 'application/vnd.ms-powerpoint',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
];
return $types[$ext] ?? 'application/octet-stream';
}
?>