Files
crm.clientright.ru/crm_extensions/file_storage/NextcloudClient.php
Fedor f582cf9c0f fix: Обновлены пути Nextcloud с /crm2/ на /crm/ для нового сервера
Обновленные файлы:
- crm_extensions/nextcloud_editor/js/nextcloud-editor.js (5 путей)
- crm_extensions/file_storage/api/get_edit_urls.php (6 путей)
- modules/Documents/actions/NcPrepareEdit.php (2 пути)
- crm_extensions/file_storage/api/prepare_edit.php (1 путь)
- crm_extensions/file_storage/NextcloudClient.php (1 путь)
- data/CRMEntity.php (nc_path для новых файлов)

Все пути теперь используют /crm/ вместо /crm2/ для соответствия новому External Storage на office.clientright.ru
2025-10-20 17:23:20 +03:00

368 lines
13 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
/**
* Nextcloud WebDAV Client (Fixed)
* Клиент для работы с Nextcloud через WebDAV API
*/
class NextcloudClient {
private $baseUrl;
private $username;
private $password;
private $activeFolder;
public function __construct($config) {
$this->baseUrl = rtrim($config['base_url'], '/');
$this->username = $config['username'];
$this->password = $config['password'];
$this->activeFolder = trim($config['active_folder'], '/');
}
/**
* Загрузка файла в Nextcloud
*/
public function uploadFile($localPath, $remotePath) {
try {
// Убираем activeFolder из remotePath если он уже там есть
if (strpos($remotePath, $this->activeFolder) === 0) {
$fullRemotePath = $remotePath;
} else {
$fullRemotePath = $this->activeFolder . '/' . ltrim($remotePath, '/');
}
$url = $this->baseUrl . '/remote.php/dav/files/' . $this->username . '/' . $fullRemotePath;
echo "Uploading to URL: $url\n";
// Создаём папку если не существует
$this->ensureFolderExists(dirname($fullRemotePath));
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_INFILE => fopen($localPath, 'r'),
CURLOPT_INFILESIZE => filesize($localPath),
CURLOPT_USERPWD => $this->username . ':' . $this->password,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
CURLOPT_HTTPHEADER => [
'Content-Type: ' . $this->getMimeType($localPath)
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new Exception('Curl error: ' . $error);
}
if ($httpCode < 200 || $httpCode >= 300) {
throw new Exception('HTTP error: ' . $httpCode . ' - ' . $response);
}
return [
'success' => true,
'remote_path' => $fullRemotePath,
'url' => $url
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Создание папки (публичный метод)
*/
public function createFolder($folderPath) {
try {
$this->ensureFolderExists($folderPath);
return [
'success' => true,
'folder_path' => $folderPath
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Создание папки (улучшенная версия)
*/
private function ensureFolderExists($folderPath) {
if (empty($folderPath) || $folderPath === '.') {
return;
}
// Разбиваем путь на части
$parts = explode('/', trim($folderPath, '/'));
$currentPath = '';
foreach ($parts as $part) {
if (empty($part)) continue;
$currentPath .= '/' . $part;
$url = $this->baseUrl . '/remote.php/dav/files/' . $this->username . $currentPath;
echo "Creating folder: $url\n";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_CUSTOMREQUEST => 'MKCOL',
CURLOPT_USERPWD => $this->username . ':' . $this->password,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
echo "Folder creation result: HTTP $httpCode\n";
// 201 = created, 405 = already exists, оба OK
if ($httpCode !== 201 && $httpCode !== 405) {
echo "Response: $response\n";
if ($error) {
throw new Exception("Curl error creating folder '$currentPath': $error");
} else {
throw new Exception("HTTP error creating folder '$currentPath': $httpCode - $response");
}
}
}
}
/**
* Получение ID файла из Nextcloud
*/
public function getFileId($remotePath) {
try {
$fullRemotePath = $this->activeFolder . '/' . ltrim($remotePath, '/');
$url = $this->baseUrl . '/remote.php/dav/files/' . $this->username . '/' . $fullRemotePath;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_CUSTOMREQUEST => 'PROPFIND',
CURLOPT_USERPWD => $this->username . ':' . $this->password,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/xml',
'Depth: 0'
],
CURLOPT_POSTFIELDS => '<?xml version="1.0" encoding="UTF-8"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<oc:fileid/>
</d:prop>
</d:propfind>'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 207 && $response) {
$xml = simplexml_load_string($response);
if ($xml) {
foreach ($xml->xpath('//oc:fileid') as $element) {
return (string)$element;
}
}
}
return null;
} catch (Exception $e) {
error_log("Error getting file ID: " . $e->getMessage());
return null;
}
}
/**
* Создание прямой ссылки для редактирования файла
*/
public function createDirectEditLink($remotePath, $recordId, $fileName = null) {
// Создаем ссылку на файловый менеджер Nextcloud с открытием файла
if ($fileName === null) {
$fileName = basename($remotePath);
}
// Убираем подчеркивание в начале, если есть
if (substr($fileName, 0, 1) === '_') {
$fileName = substr($fileName, 1);
}
$editUrl = $this->baseUrl . '/apps/files/?dir=/crm/CRM_Active_Files/Documents/' . $recordId . '&openfile=' . urlencode($fileName);
return [
'success' => true,
'edit_url' => $editUrl,
'direct_url' => $editUrl,
'file_id' => null
];
}
/**
* Создание ссылки для редактирования файла
*/
public function createEditLink($remotePath, $permissions = 3) {
try {
$fullRemotePath = $this->activeFolder . '/' . ltrim($remotePath, '/');
// Создаём публичную ссылку через OCS API
$url = $this->baseUrl . '/ocs/v2.php/apps/files_sharing/api/v1/shares';
$postData = http_build_query([
'path' => '/' . $fullRemotePath,
'shareType' => 3, // Public link
'permissions' => $permissions, // 1=read, 2=update, 3=read+update
'format' => 'json'
]);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_USERPWD => $this->username . ':' . $this->password,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'OCS-APIRequest: true',
'Content-Type: application/x-www-form-urlencoded'
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception('Failed to create share link: HTTP ' . $httpCode . ' - ' . $response);
}
$data = json_decode($response, true);
if (!$data || $data['ocs']['meta']['statuscode'] !== 200) {
$message = $data['ocs']['meta']['message'] ?? 'Unknown OCS error';
throw new Exception('OCS error: ' . $message);
}
$shareUrl = $data['ocs']['data']['url'];
// Для редактирования добавляем параметр
$editUrl = $shareUrl . '/edit';
return [
'success' => true,
'share_url' => $shareUrl,
'edit_url' => $editUrl,
'share_id' => $data['ocs']['data']['id']
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Проверка существования файла
*/
public function fileExists($remotePath) {
$fullRemotePath = $this->activeFolder . '/' . ltrim($remotePath, '/');
$url = $this->baseUrl . '/remote.php/dav/files/' . $this->username . '/' . $fullRemotePath;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_CUSTOMREQUEST => 'HEAD',
CURLOPT_USERPWD => $this->username . ':' . $this->password,
CURLOPT_NOBODY => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_RETURNTRANSFER => true
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode === 200;
}
/**
* Скачивание файла из Nextcloud
*/
public function downloadToTemp($remotePath) {
$fullRemotePath = $this->activeFolder . '/' . ltrim($remotePath, '/');
$url = $this->baseUrl . '/remote.php/dav/files/' . $this->username . '/' . $fullRemotePath;
$tempFile = sys_get_temp_dir() . '/' . uniqid('nextcloud_download_') . '_' . basename($remotePath);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_USERPWD => $this->username . ':' . $this->password,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120
]);
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception('Download failed: HTTP ' . $httpCode);
}
file_put_contents($tempFile, $result);
return $tempFile;
}
/**
* Удаление файла
*/
public function deleteFile($remotePath) {
$fullRemotePath = $this->activeFolder . '/' . ltrim($remotePath, '/');
$url = $this->baseUrl . '/remote.php/dav/files/' . $this->username . '/' . $fullRemotePath;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_USERPWD => $this->username . ':' . $this->password,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode === 204; // 204 = No Content (успешное удаление)
}
/**
* Получение MIME типа файла
*/
private function getMimeType($filePath) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $filePath);
finfo_close($finfo);
return $mimeType ?: 'application/octet-stream';
}
}