['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; } }