class FileService
{
private const DEFAULT_MAX_BYTES = 8 * 1024 * 1024; // 8 MiB
+
+ // Конфигурация для корректировки URL изображений KIK Feedback
+ private const API2_URLS = [
+ 'erp.erp-flowers.ru' => 'https://api2.erp.erp-flowers.ru',
+ 'dev.erp-flowers.ru' => 'https://api2.dev.erp-flowers.ru',
+ 'dev1.erp-flowers.ru' => 'https://api2.dev1.erp-flowers.ru',
+ 'localhost' => 'http://localhost:5555',
+ ];
+
+ private const API2_DEFAULT_URL = 'https://api2.erp.erp-flowers.ru';
+ private const PRODUCTION_ECQ_URL = 'https://erp.erp-flowers.ru';
+ private const URL_CHECK_CACHE_TTL = 300; // 5 минут
+ private const ALLOWED_HOSTS = [
+ 'erp.erp-flowers.ru',
+ 'dev.erp-flowers.ru',
+ 'dev1.erp-flowers.ru',
+ 'localhost',
+ ];
+
public static function uploadFile($label, $admin_id) {
if (isset($_FILES[$label]["name"])) {
$isMultiple = is_array($_FILES[$label]["name"]);
/**
* Получает базовый URL для api2 в зависимости от текущего домена
+ *
+ * @return string URL api2 для текущего окружения
*/
private static function getApi2BaseUrl(): string
{
- $currentHost = $_SERVER['HTTP_HOST'] ?? 'localhost';
+ // Используем SERVER_NAME вместо HTTP_HOST для безопасности
+ $currentHost = $_SERVER['SERVER_NAME'] ?? $_SERVER['HTTP_HOST'] ?? 'localhost';
- // Для продакшена
- if ($currentHost === 'erp.erp-flowers.ru') {
- return 'https://api2.erp.erp-flowers.ru';
- }
-
- // Для dev сервера
- if ($currentHost === 'dev.erp-flowers.ru') {
- return 'https://api2.dev.erp-flowers.ru';
- }
-
- // Для dev1 сервера
- if ($currentHost === 'dev1.erp-flowers.ru') {
- return 'https://api2.dev1.erp-flowers.ru';
- }
-
- // Для локальной разработки (предполагаем localhost:5555 для api2)
- if (str_contains($currentHost, 'localhost')) {
- return 'http://localhost:5555';
+ // Проверяем хост в белом списке для безопасности
+ if (in_array($currentHost, self::ALLOWED_HOSTS, true)) {
+ return self::API2_URLS[$currentHost] ?? self::API2_DEFAULT_URL;
}
// По умолчанию продакшен
- return 'https://api2.erp.erp-flowers.ru';
+ return self::API2_DEFAULT_URL;
}
/**
- * Проверяет доступность URL
+ * Проверяет доступность URL с кэшированием результатов
+ *
+ * @param string $url URL для проверки
+ * @return bool true если URL доступен, false если нет
*/
private static function checkUrlAvailability(string $url): bool
{
+ $cacheKey = 'kik_image_url_' . md5($url);
+ $cache = Yii::$app->cache;
+
+ // Проверяем кэш
+ $cachedResult = $cache->get($cacheKey);
+ if ($cachedResult !== false) {
+ return (bool)$cachedResult;
+ }
+
+ // Определяем, нужна ли верификация SSL
+ $isLocal = str_contains($url, 'localhost') || str_contains($url, 'dev');
+
$client = new Client([
- 'timeout' => 5,
- 'connect_timeout' => 2,
- 'verify' => false, // Для тестовых сред
+ 'timeout' => 3,
+ 'connect_timeout' => 1,
+ 'verify' => !$isLocal, // Отключаем верификацию только для local/dev
+ 'http_errors' => false, // Не выбрасываем исключение на HTTP ошибок
]);
try {
$response = $client->head($url);
- return $response->getStatusCode() === 200;
+ $result = $response->getStatusCode() === 200;
+ } catch (GuzzleException $e) {
+ // Логируем ошибку для диагностики
+ Yii::warning(
+ "KIK Image URL check failed for {$url}: " . $e->getMessage(),
+ __METHOD__
+ );
+ $result = false;
} catch (\Exception $e) {
- return false;
+ // Ловим другие возможные исключения
+ Yii::warning(
+ "Unexpected error checking URL {$url}: " . $e->getMessage(),
+ __METHOD__
+ );
+ $result = false;
}
+
+ // Кэшируем результат проверки
+ $cache->set($cacheKey, $result ? 1 : 0, self::URL_CHECK_CACHE_TTL);
+
+ return $result;
}
/**
* Получает корректный URL для изображения с проверкой доступности
+ *
+ * На продакшене возвращает текущий URL без дополнительных проверок.
+ * На dev/local сначала проверяет текущий, затем fallback на продакшен.
+ *
+ * @param string $relativePath Относительный путь к файлу (например /uploads/...)
+ * @return string Полный URL изображения
*/
private static function getCorrectImageUrl(string $relativePath): string
{
$currentApi2Url = self::getApi2BaseUrl() . $relativePath;
- $prodApi2Url = 'https://api2.erp.erp-flowers.ru' . $relativePath;
+ $prodApi2Url = self::API2_DEFAULT_URL . $relativePath;
- // Если текущий URL доступен, используем его
+ // На продакшене не проверяем альтернативные URL
+ if ($currentApi2Url === $prodApi2Url) {
+ return $currentApi2Url;
+ }
+
+ // На dev/local сначала проверяем текущий
if (self::checkUrlAvailability($currentApi2Url)) {
return $currentApi2Url;
}
- // Если текущий недоступен, пробуем продакшен
+ // Fallback на продакшен
if (self::checkUrlAvailability($prodApi2Url)) {
return $prodApi2Url;
}
- // Ð\95Ñ\81ли ниÑ\87его не доÑ\81Ñ\82Ñ\83пно, возвÑ\80аÑ\89аем Ñ\82екÑ\83Ñ\89ий (лÑ\83Ñ\87Ñ\88е показаÑ\82Ñ\8c оÑ\88ибкÑ\83 чем ничего)
+ // Ð\95Ñ\81ли ниÑ\87его не доÑ\81Ñ\82Ñ\83пно, возвÑ\80аÑ\89аем Ñ\82екÑ\83Ñ\89ий (показаÑ\82Ñ\8c оÑ\88ибкÑ\83 лÑ\83Ñ\87Ñ\88е, чем ничего)
return $currentApi2Url;
}
public static function drawFile($file) {
if ($file->file_type == 'image') {
$url = $file->url;
+
// Корректировка URL для файлов kikfeedbackrequest_file с типом image
if ($file->entity == 'kikfeedbackrequest_file') {
if (str_starts_with($url, '/uploads/')) {
+ // Относительный путь - нужно добавить полный URL
$url = self::getCorrectImageUrl($url);
} else {
- $url = str_replace('https://erp.erp-flowers.ru', self::getApi2BaseUrl(), $url);
+ $newBaseUrl = self::getApi2BaseUrl();
+
+ // Заменяем старый URL на текущий базовый URL
+ if (str_starts_with($url, self::PRODUCTION_ECQ_URL)) {
+ $url = str_replace(self::PRODUCTION_ECQ_URL, $newBaseUrl, $url);
+ }
}
}
+
ImageHelper::drawImage($url);
} else {
?><a href="<?= Url::to(['/files/download', 'url' => $file->url]) ?>" class="btn btn-link" target="_blank" data-pjax="0"><?= basename($file->url)?></a><?php