Files
crm.clientright.ru/crm_extensions/file_storage/FilePathManager.php

276 lines
10 KiB
PHP
Raw Normal View History

<?php
/**
* FilePathManager - Универсальный менеджер путей файлов
*
* Единая точка для генерации путей файлов в S3 для всех модулей CRM
* Поддерживает универсальную структуру: Documents/{ModuleName}/{RecordName}_{RecordId}/{FileName}_{DocumentId}.ext
*
* Примеры:
* - Project: Documents/Иванов_Против_ООО_123/Договор_456.pdf
* - Contacts: Documents/Contacts/Петров_Иван_789/Паспорт_101.pdf
* - Accounts: Documents/Accounts/ООО_Ромашка_555/Договор_666.docx
*
* @author AI Assistant
* @date 2025-10-22
*/
class FilePathManager {
private $adb;
private $prefix = 'crm2/CRM_Active_Files/Documents';
// Конфигурация полей для получения названия записи
private $moduleFieldMap = [
'Project' => ['field' => 'projectname', 'table' => 'vtiger_project', 'id' => 'projectid'],
'Contacts' => ['field' => 'CONCAT(firstname, " ", lastname)', 'table' => 'vtiger_contactdetails', 'id' => 'contactid'],
'Accounts' => ['field' => 'accountname', 'table' => 'vtiger_account', 'id' => 'accountid'],
'HelpDesk' => ['field' => 'title', 'table' => 'vtiger_troubletickets', 'id' => 'ticketid'],
'Invoice' => ['field' => 'subject', 'table' => 'vtiger_invoice', 'id' => 'invoiceid'],
'Leads' => ['field' => 'CONCAT(firstname, " ", lastname)', 'table' => 'vtiger_leaddetails', 'id' => 'leadid'],
];
public function __construct() {
global $adb;
$this->adb = $adb;
}
/**
* Санитизация имени файла/папки
* Заменяет проблемные символы на подчеркивания
*
* @param string $name Исходное имя
* @return string Санитизированное имя
*/
public function sanitizeFileName($name) {
if (empty($name)) {
return '';
}
// Декодируем HTML entities
$name = html_entity_decode($name, ENT_QUOTES, 'UTF-8');
// Заменяем проблемные символы (включая №)
$name = str_replace(["/", "\\", ":", "*", "?", "\"", "<", ">", "|", ""], '_', $name);
// Заменяем все пробелы и запятые на подчеркивания
$name = preg_replace('/[\s,]+/', '_', $name);
// Убираем повторяющиеся подчеркивания
$name = preg_replace('/_+/', '_', $name);
return trim($name, '_');
}
/**
* Получить название записи из базы данных
*
* @param string $module Название модуля
* @param int $recordId ID записи
* @return string|null Название записи или null
*/
public function getRecordName($module, $recordId) {
if (!isset($this->moduleFieldMap[$module])) {
return null;
}
$config = $this->moduleFieldMap[$module];
try {
$query = "SELECT {$config['field']} as name FROM {$config['table']} WHERE {$config['id']} = ?";
$result = $this->adb->pquery($query, [$recordId]);
if ($this->adb->num_rows($result) > 0) {
$name = $this->adb->query_result($result, 0, 'name');
return $this->sanitizeFileName($name);
}
} catch (Exception $e) {
error_log("FilePathManager: Error getting record name for $module:$recordId - " . $e->getMessage());
}
return null;
}
/**
* Сгенерировать путь к папке записи
*
* @param string $module Название модуля
* @param int $recordId ID записи
* @param string|null $recordName Название записи (опционально, будет получено из БД)
* @return string Путь к папке
*/
public function getRecordFolderPath($module, $recordId, $recordName = null) {
// Если название не передано, получаем из базы
if ($recordName === null) {
$recordName = $this->getRecordName($module, $recordId);
} else {
$recordName = $this->sanitizeFileName($recordName);
}
// Формируем имя папки: ModuleName/название_ID
$folderName = $recordName ? "{$recordName}_{$recordId}" : "{$module}_{$recordId}";
$folderName = "{$module}/{$folderName}";
return "{$this->prefix}/{$folderName}";
}
/**
* Сгенерировать полный путь к файлу
*
* @param string $module Название модуля
* @param int $recordId ID записи
* @param int $documentId ID документа
* @param string $fileName Имя файла
* @param string|null $documentTitle Название документа (опционально)
* @param string|null $recordName Название записи (опционально)
* @return string Полный путь к файлу
*/
public function getFilePath($module, $recordId, $documentId, $fileName, $documentTitle = null, $recordName = null) {
// Получаем путь к папке
$folderPath = $this->getRecordFolderPath($module, $recordId, $recordName);
// Извлекаем расширение
$extension = $this->extractExtension($fileName);
// Формируем имя файла
if ($documentTitle) {
$sanitizedTitle = $this->sanitizeFileName($documentTitle);
$newFileName = "{$sanitizedTitle}_{$documentId}";
} else {
$newFileName = "document_{$documentId}";
}
// Добавляем расширение
if ($extension) {
$newFileName .= ".{$extension}";
}
return "{$folderPath}/{$newFileName}";
}
/**
* Извлечь расширение файла
*
* @param string $fileName Имя файла
* @return string|null Расширение без точки
*/
private function extractExtension($fileName) {
$fileName = basename($fileName);
$dotPos = strrpos($fileName, '.');
if ($dotPos !== false && $dotPos < strlen($fileName) - 1) {
return strtolower(substr($fileName, $dotPos + 1));
}
return null;
}
/**
* Проверить, поддерживается ли модуль
*
* @param string $module Название модуля
* @return bool
*/
public function isModuleSupported($module) {
return isset($this->moduleFieldMap[$module]);
}
/**
* Получить список поддерживаемых модулей
*
* @return array
*/
public function getSupportedModules() {
return array_keys($this->moduleFieldMap);
}
/**
* Парсить путь файла и получить информацию
* Поддерживает как старую, так и новую структуру
*
* @param string $filePath Путь к файлу
* @return array|null ['module' => string, 'recordId' => int, 'documentId' => int, 'fileName' => string] или null
*/
public function parseFilePath($filePath) {
// Убираем домен и bucket если есть
$filePath = preg_replace('#^https?://[^/]+/[^/]+/#', '', $filePath);
// Убираем префикс
$filePath = str_replace($this->prefix . '/', '', $filePath);
// Проверяем структуру пути
$parts = explode('/', $filePath);
$partsCount = count($parts);
// Новая структура с модулем: Module/название_recordId/файл_documentId.ext (3 части)
if ($partsCount == 3 && $this->isModuleSupported($parts[0])) {
$module = $parts[0];
$folderName = $parts[1];
$fileName = $parts[2];
// Извлекаем recordId из имени папки (название_ID)
if (preg_match('/_(\d+)$/', $folderName, $idMatch)) {
$recordId = (int)$idMatch[1];
} else {
return null;
}
// Извлекаем documentId из имени файла
if (preg_match('/_(\d+)\.[^.]+$/', $fileName, $docMatch)) {
$documentId = (int)$docMatch[1];
} else {
return null;
}
return [
'module' => $module,
'recordId' => $recordId,
'documentId' => $documentId,
'fileName' => $fileName
];
}
// Project структура: название_recordId/файл_documentId.ext (2 части)
if ($partsCount == 2) {
$folderName = $parts[0];
$fileName = $parts[1];
// Извлекаем recordId из имени папки (название_ID)
if (preg_match('/_(\d+)$/', $folderName, $idMatch)) {
$recordId = (int)$idMatch[1];
} else {
return null;
}
// Извлекаем documentId из имени файла
if (preg_match('/_(\d+)\.[^.]+$/', $fileName, $docMatch)) {
$documentId = (int)$docMatch[1];
} else {
return null;
}
return [
'module' => 'Project',
'recordId' => $recordId,
'documentId' => $documentId,
'fileName' => $fileName
];
}
// Старая структура: documentId/файл.ext
if (preg_match('#^(\d+)/([^/]+)$#', $filePath, $matches)) {
$documentId = (int)$matches[1];
$fileName = $matches[2];
return [
'module' => null,
'recordId' => null,
'documentId' => $documentId,
'fileName' => $fileName,
'isOldStructure' => true
];
}
return null;
}
}