Files
crm.clientright.ru/crm_extensions/file_storage/api/s3_proxy.php
Fedor 269c7ea216 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)
2025-11-01 01:02:03 +03:00

122 lines
4.0 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
/**
* S3 Proxy для OnlyOffice
* Проксирует запросы к S3, чтобы OnlyOffice мог загружать файлы
*/
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', 0);
$path = isset($_GET['path']) ? $_GET['path'] : '';
if (empty($path)) {
http_response_code(400);
die('Path parameter is required');
}
// CORS preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, HEAD, OPTIONS');
header('Access-Control-Allow-Headers: *');
header('Access-Control-Max-Age: 3600');
http_response_code(200);
exit;
}
// Для HEAD запросов - только headers, без body
$isHeadRequest = ($_SERVER['REQUEST_METHOD'] === 'HEAD');
// Формируем URL к S3
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
$s3Url = 'https://s3.twcstorage.ru/' . $bucket . '/' . $path;
// Проверяем Range header (для OnlyOffice partial requests)
$rangeHeader = isset($_SERVER['HTTP_RANGE']) ? $_SERVER['HTTP_RANGE'] : '';
error_log("S3 Proxy: Request from: " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
error_log("S3 Proxy: Downloading: " . $s3Url);
if ($rangeHeader) {
error_log("S3 Proxy: Range request: " . $rangeHeader);
}
// СНАЧАЛА скачиваем в буфер
$ch = curl_init($s3Url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // ← ВАЖНО: В БУФЕР!
curl_setopt($ch, CURLOPT_HEADER, true); // ← Получаем headers
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
// Для HEAD запросов - только headers
if ($isHeadRequest) {
curl_setopt($ch, CURLOPT_NOBODY, true);
}
// Если есть Range header - передаём его в S3!
if ($rangeHeader) {
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Range: ' . $rangeHeader]);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$error = curl_error($ch);
curl_close($ch);
// Проверяем ПРЕЖДЕ чем отправлять что-либо
// 200 = полный файл, 206 = частичный (Range request)
if ($response === false || ($httpCode !== 200 && $httpCode !== 206)) {
error_log("S3 Proxy ERROR: HTTP $httpCode, cURL error: $error");
header('Access-Control-Allow-Origin: *');
http_response_code($httpCode ?: 500);
die('Failed to fetch file from S3');
}
// Разделяем headers и body
$headersText = substr($response, 0, $headerSize);
$body = $isHeadRequest ? '' : substr($response, $headerSize); // Для HEAD body пустой
// Парсим headers
$headers = explode("\r\n", $headersText);
foreach ($headers as $header) {
if (strpos($header, ':') !== false) {
list($name, $value) = explode(':', $header, 2);
$name = strtolower(trim($name));
$value = trim($value);
// Пробрасываем нужные headers
if (in_array($name, ['content-type', 'content-length', 'content-range', 'accept-ranges', 'etag', 'last-modified'])) {
header($name . ': ' . $value);
}
}
}
// CORS headers
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, HEAD, OPTIONS');
header('Access-Control-Allow-Headers: *');
header('Access-Control-Expose-Headers: Content-Range, Accept-Ranges');
// Устанавливаем правильный HTTP код (206 для partial content)
if ($httpCode === 206) {
http_response_code(206);
} else {
http_response_code(200);
}
// Отправляем body только для GET запросов (не для HEAD)
if (!$isHeadRequest) {
echo $body;
error_log("S3 Proxy: Success! Sent " . strlen($body) . " bytes");
} else {
error_log("S3 Proxy: HEAD request completed");
}
?>