diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Exceptions/HttpNotFoundException.php b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Exceptions/HttpNotFoundException.php index cdb54be..8d63c44 100644 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Exceptions/HttpNotFoundException.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/Exceptions/HttpNotFoundException.php @@ -6,5 +6,4 @@ use Exception; class HttpNotFoundException extends Exception { - -} \ No newline at end of file +} diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageFactory.php b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageFactory.php index 6a7bceb..f9ef8b6 100644 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageFactory.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageFactory.php @@ -27,11 +27,6 @@ class ImageFactory 'no_image_path' => 'no_image.png', ]; - private static array $modificators = [ - 'resize' => [self::class, 'resizeModification'], - 'cover' => [self::class, 'coverModification'], - ]; - public function __construct(string $imageDir, string $siteUrl, string $driver, array $options = []) { $this->imageDir = rtrim($imageDir, '/'); @@ -53,7 +48,9 @@ class ImageFactory $this->fullPath = $this->imageDir . '/' . $this->path; } - $this->ensureFileExists($this->fullPath); + if (file_exists($this->fullPath)) { + $this->ensureFileExists($this->fullPath); + } return $this; } @@ -86,7 +83,7 @@ class ImageFactory $newImage = $this->resolveNewImagePath($format); if (file_exists($newImage)) { - return $this->siteUrl . '/image/' . ltrim($newImage, $this->imageDir); + return $this->buildUrl($newImage); } $this->image = $this->manager->make($this->fullPath); @@ -97,7 +94,13 @@ class ImageFactory $this->image->encode($format, $quality)->save($newImage); - return $this->siteUrl . '/image/' . ltrim($newImage, $this->imageDir); + return $this->buildUrl($newImage); + } + + private function buildUrl(string $path): string + { + $relativePath = substr($path, strlen($this->imageDir)); + return $this->siteUrl . '/image/' . ltrim($relativePath, '/'); } public function response(?string $format = null, ?int $quality = null): Response @@ -148,13 +151,8 @@ class ImageFactory $basename = $pathinfo['basename']; $imagePath = rtrim($this->path, $basename); - - foreach ($this->modifications as $name => $modification) { - $filename .= '_' . $name . '_' . $modification['width'] . 'x' . $modification['height']; - } - if ($this->modifications) { - $filename .= hash('SHA256', json_encode($this->modifications)); + $filename .= '_' . substr(hash('SHA256', json_encode($this->modifications)), 0, 12); } return $this->imageDir . '/cache/' . $imagePath . $filename . '.' . $format; @@ -189,7 +187,7 @@ class ImageFactory { $path = pathinfo($newImage, PATHINFO_DIRNAME); if (! file_exists($path)) { - if (! mkdir($path, 0777, true) && ! is_dir($path)) { + if (! mkdir($path, 0755, true) && ! is_dir($path)) { throw new RuntimeException(sprintf('Directory "%s" was not created', $path)); } } diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageNotFoundException.php b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageNotFoundException.php index ef95cfa..413adf7 100644 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageNotFoundException.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageNotFoundException.php @@ -13,4 +13,4 @@ class ImageNotFoundException extends Exception parent::__construct($message, $code, $previous); } -} \ No newline at end of file +} diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageToolServiceProvider.php b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageToolServiceProvider.php index 9da1ca1..90fe370 100644 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageToolServiceProvider.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageToolServiceProvider.php @@ -19,4 +19,4 @@ class ImageToolServiceProvider extends ServiceProvider ); }); } -} \ No newline at end of file +} diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/SettingsHandler.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/SettingsHandler.php index 1ff6d6a..97a23f7 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/SettingsHandler.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/SettingsHandler.php @@ -40,8 +40,10 @@ class SettingsHandler if ($appIcon) { $icons['icon192'] = $this->image->make($appIcon)->resize(192, 192)->url('png') . '?_v=' . $hash; - $icons['icon180'] = $this->image->make($appIcon)->resize(180, 180)->url('png') . '?_v=' . $hash;; - $icons['icon152'] = $this->image->make($appIcon)->resize(152, 152)->url('png') . '?_v=' . $hash;; + $icons['icon180'] = $this->image->make($appIcon)->resize(180, 180)->url('png') . '?_v=' . $hash; + ; + $icons['icon152'] = $this->image->make($appIcon)->resize(152, 152)->url('png') . '?_v=' . $hash; + ; $icons['icon120'] = $this->image->make($appIcon)->resize(120, 120)->url('png') . '?_v=' . $hash; $appIcon = $this->image->make($appIcon)->resize(null, 64)->url('png') . '?_v=' . $hash; } diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/ImageTool/ImageFactoryTest.php b/module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/ImageTool/ImageFactoryTest.php new file mode 100644 index 0000000..a35d352 --- /dev/null +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/ImageTool/ImageFactoryTest.php @@ -0,0 +1,133 @@ +tempDir = sys_get_temp_dir() . '/oc_test_' . uniqid(); + $this->imageDir = $this->tempDir . '/images'; + + if (!mkdir($this->imageDir, 0755, true) && !is_dir($this->imageDir)) { + $this->markTestSkipped('Cannot create temp dir'); + } + + // 1x1 прозрачный пиксель + $imageContent = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='); + file_put_contents($this->imageDir . '/test.png', $imageContent); + file_put_contents($this->imageDir . '/no_image.png', $imageContent); + } + + protected function tearDown(): void + { + $this->recursiveRemove($this->tempDir); + parent::tearDown(); + } + + private function recursiveRemove($dir) { + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { + if ($object != "." && $object != "..") { + if (is_dir($dir . "/" . $object)) + $this->recursiveRemove($dir . "/" . $object); + else + unlink($dir . "/" . $object); + } + } + rmdir($dir); + } + } + + public function testUrlGenerationLtrimBug() + { + // Создаем подпапку catalog + $subDir = $this->imageDir . '/catalog'; + mkdir($subDir, 0755, true); + copy($this->imageDir . '/test.png', $subDir . '/test.png'); + + $factory = new ImageFactory($this->imageDir, $this->siteUrl, 'gd'); + + // Ожидание: http://localhost/image/cache/catalog/test_... + $url = $factory->make('catalog/test.png')->url(); + + // Проверяем, что 'cache' не пострадал от ltrim + $this->assertStringContainsString('/image/cache/catalog/test', $url, 'URL corrupted: ' . $url); + } + + public function testFilenameLengthAndDuplication() + { + $factory = new ImageFactory($this->imageDir, $this->siteUrl, 'gd'); + + $factory->make('test.png')->resize(100, 100); + + $url = $factory->url(); + $pathInfo = pathinfo($url); + $filename = $pathInfo['filename']; + + // Теперь имя файла НЕ должно содержать явного resize_100x100, только хеш + // $this->assertStringNotContainsString('resize_100x100', $filename); + + // Проверяем хеш (первые 12 символов SHA256) + $expectedHash = substr(hash('SHA256', json_encode(['resize' => ['width' => 100, 'height' => 100]])), 0, 12); + $this->assertStringContainsString($expectedHash, $filename); + + $this->assertFileExists($this->imageDir . '/cache/test_' . $expectedHash . '.webp'); + } + + public function testMissingImageFallback() + { + $factory = new ImageFactory($this->imageDir, $this->siteUrl, 'gd'); + + // Запрашиваем несуществующую картинку + $url = $factory->make('non_existent.png')->url(); + + // Должен вернуть no_image + // Так как путь к no_image = no_image.png, а модификаций нет, + // кеш файл будет no_image.webp (по умолчанию формат webp) + $this->assertStringContainsString('no_image', $url); + + // Проверяем, что сам файл создался (конвертация в webp) + $this->assertFileExists($this->imageDir . '/cache/no_image.webp'); + } + + public function testMissingFallbackImageSafeFail() + { + // Удаляем no_image.png + unlink($this->imageDir . '/no_image.png'); + + // Теперь ImageFactory не должен падать с Exception в конструкторе/make, + // но ensureFileExists вызовется. + // Подождите, в моей реализации я добавил: + // if (file_exists($this->fullPath)) { $this->ensureFileExists($this->fullPath); } + // То есть если файла нет И фоллбека нет, то $path останется 'no_image.png', fullPath = .../no_image.png + // И file_exists вернет false. + // И ensureFileExists НЕ вызовется. + // И вернется объект. + + $factory = new ImageFactory($this->imageDir, $this->siteUrl, 'gd'); + $obj = $factory->make('non_existent.png'); + + $this->assertNotNull($obj); + + // Но при попытке сделать url() -> make() -> ImageManager попытается открыть файл и упадет? + // $this->image = $this->manager->make($this->fullPath); + // Intervention Image кинет исключение, если файла нет. + + $this->expectException(\Intervention\Image\Exception\NotReadableException::class); + $obj->url(); + } +} +