✅ Что реализовано: - SSL/HTTPS для Nextcloud (Let's Encrypt R13) - Redis кэширование для производительности - Collabora Online редактор документов - WOPI allow list настроен (0.0.0.0/0) - Динамическое получение fileId через WebDAV - Поддержка файлов из S3 и локальных файлов - Автоматическое извлечение имени файла из URL - Промежуточная страница для обхода CSRF 🚀 Как работает: 1. JavaScript передает recordId и fileName 2. PHP получает fileId через WebDAV PROPFIND 3. PHP делает редирект на рабочий URL Nextcloud 4. Файл открывается в редакторе Collabora 📁 Файлы: - layouts/v7/lib/nextcloud-editor.js - JavaScript интеграция - crm_extensions/file_storage/api/open_file.php - PHP редирект - modules/Documents/actions/NcPrepareEdit.php - API подготовка - crm_extensions/docs/ - документация 🎯 Результат: Каждый документ в CRM открывает СВОЙ файл в Nextcloud редакторе!
442 lines
18 KiB
JavaScript
442 lines
18 KiB
JavaScript
/**
|
||
* Nextcloud Editor Integration JavaScript
|
||
* JavaScript для интеграции редактора документов Nextcloud
|
||
*/
|
||
|
||
/**
|
||
* Открытие редактора Nextcloud для документа
|
||
*/
|
||
function openNextcloudEditor(recordId, fileName) {
|
||
console.log('🚀 NEXTCLOUD EDITOR: Function called!', recordId, fileName);
|
||
|
||
// ПРОСТОЕ РЕШЕНИЕ - используем промежуточную страницу для редиректа!
|
||
const redirectUrl = `/crm_extensions/file_storage/api/open_file.php?recordId=${recordId}&fileName=${encodeURIComponent(fileName)}`;
|
||
|
||
console.log('🎯 Opening editor via redirect:', redirectUrl);
|
||
|
||
// Открываем редактор в новом окне через промежуточную страницу
|
||
const win = window.open(redirectUrl, 'nextcloud_editor', 'width=1200,height=800,scrollbars=yes,resizable=yes');
|
||
|
||
if (win) {
|
||
console.log('✅ Editor opened successfully');
|
||
alert('✅ Редактор открыт! Файл: ' + fileName);
|
||
} else {
|
||
console.log('❌ Failed to open editor window - popup blocked');
|
||
alert('❌ Не удалось открыть редактор. Проверьте блокировку всплывающих окон.');
|
||
}
|
||
}
|
||
|
||
function testSimpleAPI(recordId, fileName) {
|
||
console.log('🧪 Testing simple API...', recordId, fileName);
|
||
|
||
// Пропускаем простой API и сразу идем к основному
|
||
console.log('🎯 Skipping simple API, going to main API...');
|
||
callMainAPI(recordId, fileName);
|
||
}
|
||
|
||
function createEditUrls(baseEditUrl, recordId, fileName, fileId = 662) {
|
||
console.log('🔗 Creating edit URLs in JavaScript...', { fileId, fileName });
|
||
|
||
// Извлекаем базовый URL из базовой ссылки
|
||
const baseUrl = 'https://office.clientright.ru:8443';
|
||
const encodedFileName = encodeURIComponent(fileName);
|
||
const filePath = `/crm/crm2/CRM_Active_Files/Documents/${recordId}/${encodedFileName}`;
|
||
|
||
// Токен для RichDocuments (из настроек Nextcloud)
|
||
const richDocumentsToken = '1sanuq71b3n4fm1ldkbb';
|
||
|
||
const urls = {
|
||
// ЛУЧШИЙ СПОСОБ! - редирект через нашу промежуточную страницу (БЕЗ CSRF!)
|
||
'redirect_to_nextcloud': fileId ? `/crm_extensions/file_storage/api/open_file.php?fileId=${fileId}&fileName=${encodedFileName}&recordId=${recordId}` : null,
|
||
// РАБОЧИЙ СПОСОБ! - прямая ссылка на Nextcloud
|
||
'files_editing_auto': fileId ? `${baseUrl}/apps/files/files/${fileId}?dir=/&editing=true&openfile=true` : null,
|
||
// Вариант без автоматического редактирования
|
||
'files_editing': fileId ? `${baseUrl}/apps/files/files/${fileId}?dir=/&editing=false&openfile=true` : null,
|
||
// Collabora Editor
|
||
'collabora_editor': fileId ? `${baseUrl}/index.php/apps/richdocuments/index?fileId=${fileId}` : null,
|
||
// OnlyOffice Editor
|
||
'onlyoffice_editor': fileId ? `${baseUrl}/apps/onlyoffice?fileId=${fileId}` : null,
|
||
// Прямое открытие файла
|
||
'files_direct': fileId ? `${baseUrl}/apps/files/files/${fileId}` : `${baseUrl}/apps/files/?dir=/&openfile=${encodedFileName}`,
|
||
// Файловый менеджер
|
||
'files_manager': `${baseUrl}/apps/files/?dir=/&openfile=${encodedFileName}`
|
||
};
|
||
|
||
// Убираем null значения
|
||
Object.keys(urls).forEach(key => {
|
||
if (urls[key] === null) {
|
||
delete urls[key];
|
||
}
|
||
});
|
||
|
||
return {
|
||
all: urls,
|
||
// РЕДИРЕКТ через нашу страницу - лучший способ (обходит CSRF)
|
||
recommended: urls.redirect_to_nextcloud || urls.files_editing_auto || urls.files_editing || urls.collabora_editor
|
||
};
|
||
}
|
||
|
||
function getEditUrls(recordId, fileName, simpleData) {
|
||
console.log('🔗 Getting edit URLs...');
|
||
|
||
$.ajax({
|
||
url: '/crm_extensions/file_storage/api/get_edit_urls.php',
|
||
method: 'GET',
|
||
data: {
|
||
record: recordId,
|
||
fileName: fileName
|
||
},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
console.log('✅ Edit URLs received:', response);
|
||
|
||
if (response.success) {
|
||
// Пробуем открыть рекомендуемый URL
|
||
const recommendedUrl = response.data.urls[response.data.recommended];
|
||
console.log('🎯 Trying recommended URL:', recommendedUrl);
|
||
|
||
openEditor(recommendedUrl, {
|
||
...simpleData,
|
||
urls: response.data.urls,
|
||
recommended: response.data.recommended
|
||
});
|
||
} else {
|
||
// Если не получилось, используем простой API
|
||
openEditor(simpleData.edit_url, simpleData);
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
console.error('❌ Failed to get edit URLs:', error);
|
||
// Если не получилось, используем простой API
|
||
openEditor(simpleData.edit_url, simpleData);
|
||
}
|
||
});
|
||
}
|
||
|
||
function callMainAPI(recordId, fileName) {
|
||
console.log('🎯 Calling main API...', recordId, fileName);
|
||
|
||
// Показываем прогресс
|
||
if (typeof app !== 'undefined' && app.helper && app.helper.showProgress) {
|
||
app.helper.showProgress({
|
||
message: 'Подготовка файла к редактированию...'
|
||
});
|
||
}
|
||
|
||
// Вызываем ПРАВИЛЬНЫЙ API для подготовки файла
|
||
$.ajax({
|
||
url: '/index.php?module=Documents&action=NcPrepareEdit',
|
||
method: 'GET',
|
||
data: {
|
||
record: recordId,
|
||
fileName: fileName
|
||
},
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
console.log('📡 API Response:', response);
|
||
|
||
// Скрываем прогресс
|
||
if (typeof app !== 'undefined' && app.helper && app.helper.hideProgress) {
|
||
app.helper.hideProgress();
|
||
}
|
||
|
||
if (response.success) {
|
||
console.log('✅ File prepared successfully');
|
||
|
||
// Открываем редактор
|
||
openEditor(response.data.edit_url, response.data);
|
||
|
||
} else {
|
||
console.error('❌ API Error:', response.error);
|
||
showError('Ошибка подготовки файла: ' + response.error);
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
console.error('❌ AJAX Error:', error);
|
||
console.error('❌ Status:', status);
|
||
console.error('❌ Response:', xhr.responseText);
|
||
console.error('❌ Status Code:', xhr.status);
|
||
|
||
// Скрываем прогресс
|
||
if (typeof app !== 'undefined' && app.helper && app.helper.hideProgress) {
|
||
app.helper.hideProgress();
|
||
}
|
||
|
||
// Показываем подробную ошибку
|
||
let errorMessage = 'Ошибка подключения к серверу: ' + error;
|
||
if (xhr.responseText) {
|
||
errorMessage += '\n\nОтвет сервера:\n' + xhr.responseText;
|
||
}
|
||
|
||
showError(errorMessage);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Функция для открытия редактора
|
||
function openEditor(editUrl, fileData) {
|
||
console.log('🎯 Opening editor with URL:', editUrl);
|
||
console.log('📋 All available URLs:', fileData.urls);
|
||
|
||
// Открываем редактор в новом окне
|
||
var win = window.open(editUrl, 'nextcloud_editor', 'width=1200,height=800,scrollbars=yes,resizable=yes');
|
||
|
||
if (win) {
|
||
console.log('✅ Editor opened successfully');
|
||
|
||
// Показываем уведомление об успехе
|
||
if (typeof app !== 'undefined' && app.helper && app.helper.showSuccessNotification) {
|
||
app.helper.showSuccessNotification({
|
||
message: 'Редактор открыт! Файл: ' + fileData.file_name
|
||
});
|
||
} else {
|
||
alert('✅ Редактор открыт! Файл: ' + fileData.file_name);
|
||
}
|
||
|
||
// Показываем информацию о файле
|
||
showFileInfo(fileData);
|
||
|
||
} else {
|
||
console.log('❌ Failed to open editor window - popup blocked or error');
|
||
console.log('📋 Showing modal with alternative URLs');
|
||
|
||
// Показываем модальное окно с альтернативными вариантами
|
||
showModalWithUrls(editUrl, fileData);
|
||
}
|
||
}
|
||
|
||
// Функция для отображения информации о файле
|
||
function showFileInfo(fileData) {
|
||
// Проверяем, есть ли нужные поля в fileData
|
||
var location = 'Неизвестно';
|
||
var nextcloudPath = 'Не указан';
|
||
var status = 'Не указан';
|
||
|
||
if (fileData.file_location && fileData.file_location.type) {
|
||
location = fileData.file_location.type === 's3' ? 'S3 хранилище' : 'Локальное хранилище';
|
||
}
|
||
|
||
if (fileData.nextcloud_path) {
|
||
nextcloudPath = fileData.nextcloud_path;
|
||
}
|
||
|
||
if (fileData.message) {
|
||
status = fileData.message;
|
||
}
|
||
|
||
var infoHtml = `
|
||
<div class="alert alert-info" style="margin: 10px 0;">
|
||
<h5><i class="fa fa-info-circle"></i> Информация о файле</h5>
|
||
<p><strong>Файл:</strong> ${fileData.file_name}</p>
|
||
<p><strong>Расположение:</strong> ${location}</p>
|
||
<p><strong>Путь в Nextcloud:</strong> ${nextcloudPath}</p>
|
||
<p><strong>Статус:</strong> ${status}</p>
|
||
</div>
|
||
`;
|
||
|
||
// Если есть альтернативные URL, добавляем их
|
||
if (fileData.urls) {
|
||
infoHtml += `
|
||
<div class="alert alert-warning" style="margin: 10px 0;">
|
||
<h6><i class="fa fa-link"></i> Альтернативные способы открытия:</h6>
|
||
<div class="btn-group-vertical" style="width: 100%;">
|
||
`;
|
||
|
||
Object.keys(fileData.urls).forEach(function(key) {
|
||
const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||
const isRecommended = key === fileData.recommended;
|
||
const btnClass = isRecommended ? 'btn-success' : 'btn-default';
|
||
const icon = isRecommended ? 'fa-star' : 'fa-external-link';
|
||
|
||
infoHtml += `
|
||
<a href="${fileData.urls[key]}" target="_blank" class="btn ${btnClass} btn-sm" style="margin: 2px; text-align: left;">
|
||
<i class="fa ${icon}"></i> ${label}${isRecommended ? ' (рекомендуется)' : ''}
|
||
</a>
|
||
`;
|
||
});
|
||
|
||
infoHtml += `
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Добавляем информацию в модальное окно или показываем отдельно
|
||
if ($('#nextcloudEditModal').length) {
|
||
$('#nextcloudEditModal .modal-body').prepend(infoHtml);
|
||
} else {
|
||
// Создаём временное уведомление
|
||
var notification = $('<div class="alert alert-info" style="position: fixed; top: 20px; right: 20px; z-index: 9999; max-width: 400px;">' + infoHtml + '</div>');
|
||
$('body').append(notification);
|
||
|
||
// Автоматически скрываем через 5 секунд
|
||
setTimeout(function() {
|
||
notification.fadeOut(function() {
|
||
notification.remove();
|
||
});
|
||
}, 5000);
|
||
}
|
||
}
|
||
|
||
// Функция для отображения ошибок
|
||
function showError(message) {
|
||
console.error('🚨 Error:', message);
|
||
|
||
if (typeof app !== 'undefined' && app.helper && app.helper.showErrorNotification) {
|
||
app.helper.showErrorNotification({
|
||
message: message
|
||
});
|
||
} else {
|
||
alert('❌ Ошибка: ' + message);
|
||
}
|
||
}
|
||
|
||
// Функция для показа модального окна с URL
|
||
function showModalWithUrls(editUrl, fileData) {
|
||
console.log('📋 Showing modal with URLs for file:', fileData.file_name);
|
||
|
||
if (fileData.urls) {
|
||
// Используем существующую функцию showEditOptions
|
||
showEditOptions(fileData.urls, fileData.file_name, fileData.record_id);
|
||
} else {
|
||
// Если нет альтернативных URL, показываем ошибку
|
||
showError('Не удалось открыть редактор. Попробуйте открыть файл вручную в Nextcloud.');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Показ вариантов открытия файла
|
||
*/
|
||
function showEditOptions(urls, fileName, recordId) {
|
||
console.log('🎯 Showing edit options for:', fileName);
|
||
console.log('📋 Available URLs:', urls);
|
||
|
||
var buttonsHtml = '';
|
||
|
||
// Если urls - это объект (новый формат)
|
||
if (typeof urls === 'object' && !Array.isArray(urls)) {
|
||
Object.keys(urls).forEach(function(key, index) {
|
||
const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||
const url = urls[key];
|
||
const isRecommended = key === 'correct_path';
|
||
const btnClass = isRecommended ? 'btn-success' : 'btn-primary';
|
||
const icon = isRecommended ? 'fa-star' : 'fa-external-link';
|
||
|
||
buttonsHtml += `
|
||
<a href="${url}" target="_blank" class="btn ${btnClass} btn-sm" style="margin: 3px; display: block;">
|
||
<i class="fa ${icon}"></i> ${label}${isRecommended ? ' (рекомендуется)' : ''}
|
||
</a>
|
||
`;
|
||
});
|
||
} else {
|
||
// Старый формат - массив URL
|
||
var labels = [
|
||
'С параметром openfile',
|
||
'С action=edit',
|
||
'С edit=true',
|
||
'Через RichDocuments',
|
||
'Через OnlyOffice'
|
||
];
|
||
|
||
var icons = ['fa-file', 'fa-edit', 'fa-pencil', 'fa-file-text', 'fa-file-word-o'];
|
||
var colors = ['btn-primary', 'btn-success', 'btn-info', 'btn-warning', 'btn-danger'];
|
||
|
||
urls.forEach(function(url, index) {
|
||
buttonsHtml += `
|
||
<a href="${url}" target="_blank" class="btn ${colors[index]} btn-sm" style="margin: 3px; display: block;">
|
||
<i class="fa ${icons[index]}"></i> ${labels[index]}
|
||
</a>
|
||
`;
|
||
});
|
||
}
|
||
|
||
var modalHtml = `
|
||
<div class="modal fade" id="nextcloudEditModal" tabindex="-1" role="dialog">
|
||
<div class="modal-dialog" role="document">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h4 class="modal-title">
|
||
<i class="fa fa-edit"></i> Варианты открытия файла
|
||
</h4>
|
||
<button type="button" class="close" data-dismiss="modal">
|
||
<span>×</span>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p><strong>Файл:</strong> ${fileName}</p>
|
||
<p>Попробуйте эти варианты для прямого открытия в редакторе:</p>
|
||
|
||
<div class="btn-group-vertical" style="width: 100%;">
|
||
${buttonsHtml}
|
||
</div>
|
||
|
||
<div class="alert alert-info" style="margin-top: 15px;">
|
||
<small><strong>💡 Совет:</strong> Попробуйте варианты сверху вниз. Один из них должен открыть файл сразу в редакторе!</small>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Удаляем старое модальное окно
|
||
$('#nextcloudEditModal').remove();
|
||
|
||
// Добавляем новое
|
||
$('body').append(modalHtml);
|
||
$('#nextcloudEditModal').modal('show');
|
||
}
|
||
|
||
/**
|
||
* Синхронизация изменений файла (заглушка)
|
||
*/
|
||
function syncFileChanges(recordId, fileName) {
|
||
console.log('Syncing file changes for:', recordId, fileName);
|
||
|
||
if (typeof app !== 'undefined' && app.helper && app.helper.showSuccessNotification) {
|
||
app.helper.showSuccessNotification({
|
||
message: 'Изменения синхронизированы!'
|
||
});
|
||
} else {
|
||
alert('✅ Изменения синхронизированы!');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Открытие редактора в новом окне
|
||
*/
|
||
function openInNewWindow(editUrl) {
|
||
window.open(editUrl, 'nextcloud_editor', 'width=1200,height=800,scrollbars=yes,resizable=yes,toolbar=no,location=no');
|
||
console.log('Opened Nextcloud editor in new window');
|
||
}
|
||
|
||
// Алиас функции для обратной совместимости
|
||
function editInNextcloud(recordId, fileName) {
|
||
console.log('📝 editInNextcloud called (alias)');
|
||
return openNextcloudEditor(recordId, fileName);
|
||
}
|
||
|
||
// Автоматическое подключение при загрузке страницы
|
||
$(document).ready(function() {
|
||
console.log('Nextcloud Editor integration loaded');
|
||
|
||
// Добавляем CSS стили для модального окна
|
||
if (!$('#nextcloud-editor-styles').length) {
|
||
$('<style id="nextcloud-editor-styles">')
|
||
.html(`
|
||
.nextcloud-edit-btn {
|
||
background: #0082c9;
|
||
border-color: #0082c9;
|
||
}
|
||
|
||
.nextcloud-edit-btn:hover {
|
||
background: #006ba6;
|
||
border-color: #006ba6;
|
||
}
|
||
`)
|
||
.appendTo('head');
|
||
}
|
||
}); |