feat: encode images to webp for telegram mini app

This commit is contained in:
Nikita Kiselev
2025-07-20 23:35:30 +03:00
parent db24be6f92
commit c282b6ea3b
9 changed files with 445 additions and 56 deletions

View File

@@ -1,10 +1,11 @@
<?php <?php
use App\Adapters\OcImageTool;
use App\Adapters\OcModelCatalogProductAdapter; use App\Adapters\OcModelCatalogProductAdapter;
use App\ApplicationFactory; use App\ApplicationFactory;
use Cart\Currency; use Cart\Currency;
use Cart\Tax; use Cart\Tax;
use Openguru\OpenCartFramework\ImageTool\ImageTool;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop'; $sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
$basePath = rtrim(DIR_APPLICATION, '/') . '/..'; $basePath = rtrim(DIR_APPLICATION, '/') . '/..';
@@ -55,9 +56,8 @@ class Controllerextensiontgshophandle extends Controller
return new OcModelCatalogProductAdapter($this->model_catalog_product); return new OcModelCatalogProductAdapter($this->model_catalog_product);
}); });
$app->bind(OcImageTool::class, function () { $app->bind(ImageToolInterface::class, function () {
$this->load->model('tool/image'); return new ImageTool(DIR_IMAGE);
return new OcImageTool($this->model_tool_image);
}); });
$this->load->model('checkout/order'); $this->load->model('checkout/order');

View File

@@ -19,7 +19,8 @@
"php": "^7.4", "php": "^7.4",
"ext-pdo": "*", "ext-pdo": "*",
"psr/container": "^2.0", "psr/container": "^2.0",
"ext-json": "*" "ext-json": "*",
"intervention/image": "^2.7"
}, },
"require-dev": { "require-dev": {
"roave/security-advisories": "dev-latest" "roave/security-advisories": "dev-latest"

View File

@@ -4,8 +4,208 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "775a9a548899d07b9c007ecf3afe4e44", "content-hash": "4c731e75f55435f8b300c557fdfabfed",
"packages": [ "packages": [
{
"name": "guzzlehttp/psr7",
"version": "2.7.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0",
"ralouphie/getallheaders": "^3.0"
},
"provide": {
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.7.1"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
"type": "tidelift"
}
],
"time": "2025-03-27T12:30:47+00:00"
},
{
"name": "intervention/image",
"version": "2.7.2",
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
"reference": "04be355f8d6734c826045d02a1079ad658322dad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad",
"reference": "04be355f8d6734c826045d02a1079ad658322dad",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"guzzlehttp/psr7": "~1.1 || ^2.0",
"php": ">=5.4.0"
},
"require-dev": {
"mockery/mockery": "~0.9.2",
"phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15"
},
"suggest": {
"ext-gd": "to use GD library based image processing.",
"ext-imagick": "to use Imagick based image processing.",
"intervention/imagecache": "Caching extension for the Intervention Image library"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Image": "Intervention\\Image\\Facades\\Image"
},
"providers": [
"Intervention\\Image\\ImageServiceProvider"
]
},
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-4": {
"Intervention\\Image\\": "src/Intervention/Image"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@intervention.io",
"homepage": "https://intervention.io/"
}
],
"description": "Image handling and manipulation library with support for Laravel integration",
"homepage": "http://image.intervention.io/",
"keywords": [
"gd",
"image",
"imagick",
"laravel",
"thumbnail",
"watermark"
],
"support": {
"issues": "https://github.com/Intervention/image/issues",
"source": "https://github.com/Intervention/image/tree/2.7.2"
},
"funding": [
{
"url": "https://paypal.me/interventionio",
"type": "custom"
},
{
"url": "https://github.com/Intervention",
"type": "github"
}
],
"time": "2022-05-21T17:30:32+00:00"
},
{ {
"name": "psr/container", "name": "psr/container",
"version": "2.0.2", "version": "2.0.2",
@@ -58,6 +258,158 @@
"source": "https://github.com/php-fig/container/tree/2.0.2" "source": "https://github.com/php-fig/container/tree/2.0.2"
}, },
"time": "2021-11-05T16:47:00+00:00" "time": "2021-11-05T16:47:00+00:00"
},
{
"name": "psr/http-factory",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
"php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory"
},
"time": "2024-04-15T12:06:14+00:00"
},
{
"name": "psr/http-message",
"version": "2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/2.0"
},
"time": "2023-04-04T09:54:51+00:00"
},
{
"name": "ralouphie/getallheaders",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "120b605dfeb996808c31b6477290a714d356e822"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
"reference": "120b605dfeb996808c31b6477290a714d356e822",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5 || ^6.5"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"support": {
"issues": "https://github.com/ralouphie/getallheaders/issues",
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"time": "2019-03-08T08:55:37+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@@ -1026,7 +1378,8 @@
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^7.4", "php": "^7.4",
"ext-pdo": "*" "ext-pdo": "*",
"ext-json": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.6.0" "plugin-api-version": "2.6.0"

View File

@@ -0,0 +1,57 @@
<?php
namespace Openguru\OpenCartFramework\ImageTool;
use Intervention\Image\ImageManager;
class ImageTool implements ImageToolInterface
{
private string $imageDir;
private string $siteUrl;
private ImageManager $manager;
public function __construct(string $imageDir, string $siteUrl = '/')
{
$this->imageDir = rtrim($imageDir, '/') . '/';
$this->siteUrl = $siteUrl;
$driver = extension_loaded('imagick') ? 'imagick' : 'gd';
$this->manager = new ImageManager(['driver' => $driver]);
}
public function resize(string $path, int $width, int $height, ?string $default = null): ?string
{
$filename = is_file($this->imageDir . $path) ? $path : $default;
if (! $filename || ! is_file($this->imageDir . $filename)) {
return null;
}
$realPath = realpath($this->imageDir . $filename);
if (strpos($realPath, realpath($this->imageDir)) !== 0) {
return null; // безопасность
}
$extless = utf8_substr($filename, 0, utf8_strrpos($filename, '.'));
$imageNew = 'cache/' . $extless . '-' . $width . 'x' . $height . '.webp';
$fullNewPath = $this->imageDir . $imageNew;
$fullOldPath = $this->imageDir . $filename;
if (! is_file($fullNewPath) || filemtime($fullOldPath) > filemtime($fullNewPath)) {
$dirPath = dirname($fullNewPath);
if (! is_dir($dirPath)) {
mkdir($dirPath, 0777, true);
}
$image = $this->manager->make($fullOldPath)->resize($width, $height, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
$image->encode('webp', 75)->save($fullNewPath, 75, 'webp');
}
return rtrim($this->siteUrl, '/') . '/image/' . str_replace($this->imageDir, '', $fullNewPath);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Openguru\OpenCartFramework\ImageTool;
interface ImageToolInterface
{
public function resize(string $path, int $width, int $height, ?string $default = null): ?string;
}

View File

@@ -1,30 +0,0 @@
<?php
namespace App\Adapters;
use Proxy;
use RuntimeException;
class OcImageTool
{
private Proxy $image;
public function __construct(Proxy $image)
{
$this->image = $image;
}
public function resize(string $path, int $width, int $height, ?string $default = null): ?string
{
if ($path && is_file(DIR_IMAGE . $path)) {
$image = $path;
return $this->image->resize($image, $width, $height);
}
if ($default && is_file(DIR_IMAGE . $default)) {
return $this->image->resize($default, $width, $height);
}
return null;
}
}

View File

@@ -20,7 +20,7 @@ class ApplicationFactory
QueryBuilderServiceProvider::class, QueryBuilderServiceProvider::class,
CacheServiceProvider::class, CacheServiceProvider::class,
RouteServiceProvider::class, RouteServiceProvider::class,
AppServiceProvider::class AppServiceProvider::class,
]); ]);
} }
} }

View File

@@ -2,18 +2,18 @@
namespace App\Handlers; namespace App\Handlers;
use App\Adapters\OcImageTool;
use Openguru\OpenCartFramework\Http\JsonResponse; use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request; use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\QueryBuilder\Builder; use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause; use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
class CategoriesHandler class CategoriesHandler
{ {
private Builder $queryBuilder; private Builder $queryBuilder;
private OcImageTool $imageTool; private ImageToolInterface $ocImageTool;
public function __construct(Builder $queryBuilder, OcImageTool $ocImageTool) public function __construct(Builder $queryBuilder, ImageToolInterface $ocImageTool)
{ {
$this->queryBuilder = $queryBuilder; $this->queryBuilder = $queryBuilder;
$this->ocImageTool = $ocImageTool; $this->ocImageTool = $ocImageTool;

View File

@@ -2,7 +2,6 @@
namespace App\Handlers; namespace App\Handlers;
use App\Adapters\OcImageTool;
use App\Adapters\OcModelCatalogProductAdapter; use App\Adapters\OcModelCatalogProductAdapter;
use Cart\Currency; use Cart\Currency;
use Cart\Tax; use Cart\Tax;
@@ -10,6 +9,7 @@ use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Http\JsonResponse; use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request; use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Http\Response; use Openguru\OpenCartFramework\Http\Response;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\QueryBuilder\Builder; use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause; use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
use Openguru\OpenCartFramework\Support\Arr; use Openguru\OpenCartFramework\Support\Arr;
@@ -21,7 +21,7 @@ class ProductsHandler
private Tax $tax; private Tax $tax;
private Settings $settings; private Settings $settings;
private OcModelCatalogProductAdapter $ocModelCatalogProduct; private OcModelCatalogProductAdapter $ocModelCatalogProduct;
private OcImageTool $ocImageTool; private ImageToolInterface $ocImageTool;
public function __construct( public function __construct(
Builder $queryBuilder, Builder $queryBuilder,
@@ -29,7 +29,7 @@ class ProductsHandler
Tax $tax, Tax $tax,
Settings $settings, Settings $settings,
OcModelCatalogProductAdapter $ocModelCatalogProduct, OcModelCatalogProductAdapter $ocModelCatalogProduct,
OcImageTool $ocImageTool ImageToolInterface $ocImageTool
) { ) {
$this->queryBuilder = $queryBuilder; $this->queryBuilder = $queryBuilder;
$this->currency = $currency; $this->currency = $currency;
@@ -44,7 +44,7 @@ class ProductsHandler
$languageId = 1; $languageId = 1;
$page = $request->get('page', 1); $page = $request->get('page', 1);
$perPage = $request->get('perPage', 10); $perPage = $request->get('perPage', 10);
$categoryId = (int)$request->get('categoryId', 0); $categoryId = (int) $request->get('categoryId', 0);
$categoryName = ''; $categoryName = '';
$imageWidth = 200; $imageWidth = 200;
@@ -137,13 +137,13 @@ class ProductsHandler
$this->settings->get('oc_default_currency'), $this->settings->get('oc_default_currency'),
); );
if (!empty($productsImagesMap[$product['product_id']])) { if (! empty($productsImagesMap[$product['product_id']])) {
$allImages = array_merge($allImages, $productsImagesMap[$product['product_id']]); $allImages = array_merge($allImages, $productsImagesMap[$product['product_id']]);
} }
return [ return [
'id' => (int)$product['product_id'], 'id' => (int) $product['product_id'],
'product_quantity' => (int)$product['product_quantity'], 'product_quantity' => (int) $product['product_quantity'],
'name' => $product['product_name'], 'name' => $product['product_name'],
'price' => $price, 'price' => $price,
'images' => $allImages, 'images' => $allImages,
@@ -158,7 +158,7 @@ class ProductsHandler
public function show(Request $request): JsonResponse public function show(Request $request): JsonResponse
{ {
$productId = (int)$request->get('id'); $productId = (int) $request->get('id');
$languageId = 1; $languageId = 1;
$imageWidth = 500; $imageWidth = 500;
$imageHeight = 500; $imageHeight = 500;
@@ -192,7 +192,7 @@ class ProductsHandler
->limit(1) ->limit(1)
->firstOrNull(); ->firstOrNull();
if (!$product) { if (! $product) {
return new JsonResponse([], Response::HTTP_NOT_FOUND); return new JsonResponse([], Response::HTTP_NOT_FOUND);
} }
@@ -262,8 +262,8 @@ class ProductsHandler
$product_option_value_data = []; $product_option_value_data = [];
foreach ($option['product_option_value'] as $option_value) { foreach ($option['product_option_value'] as $option_value) {
if (!$option_value['subtract'] || ($option_value['quantity'] > 0)) { if (! $option_value['subtract'] || ($option_value['quantity'] > 0)) {
if ((float)$option_value['price']) { if ((float) $option_value['price']) {
$priceWithTax = $this->tax->calculate( $priceWithTax = $this->tax->calculate(
$option_value['price'], $option_value['price'],
$taxClassId, $taxClassId,
@@ -276,8 +276,8 @@ class ProductsHandler
} }
$product_option_value_data[] = [ $product_option_value_data[] = [
'product_option_value_id' => (int)$option_value['product_option_value_id'], 'product_option_value_id' => (int) $option_value['product_option_value_id'],
'option_value_id' => (int)$option_value['option_value_id'], 'option_value_id' => (int) $option_value['option_value_id'],
'name' => $option_value['name'], 'name' => $option_value['name'],
'image' => $this->ocImageTool->resize($option_value['image'], 50, 50), 'image' => $this->ocImageTool->resize($option_value['image'], 50, 50),
'price' => $price, 'price' => $price,
@@ -288,9 +288,9 @@ class ProductsHandler
} }
$result[] = [ $result[] = [
'product_option_id' => (int)$option['product_option_id'], 'product_option_id' => (int) $option['product_option_id'],
'values' => $product_option_value_data, 'values' => $product_option_value_data,
'option_id' => (int)$option['option_id'], 'option_id' => (int) $option['option_id'],
'name' => $option['name'], 'name' => $option['name'],
'type' => $option['type'], 'type' => $option['type'],
'value' => $option['value'], 'value' => $option['value'],