feat: remove cache, refactor

This commit is contained in:
2025-08-08 22:25:00 +03:00
parent 4114c3366e
commit 7404ecb33e
7 changed files with 474 additions and 395 deletions

View File

@@ -29,4 +29,7 @@ link:
dev: dev:
$(MAKE) link && \ $(MAKE) link && \
cd spa && npm run dev cd spa && npm run dev
lint:
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash -c "./vendor/bin/phpstan analyse src framework"

View File

@@ -12,7 +12,7 @@ services:
deploy: deploy:
resources: resources:
limits: limits:
memory: 256M memory: 512M
environment: environment:
- WEB_DOCUMENT_ROOT=/web/upload - WEB_DOCUMENT_ROOT=/web/upload
- PHP_DISPLAY_ERRORS=1 - PHP_DISPLAY_ERRORS=1

View File

@@ -27,6 +27,7 @@
"symfony/cache": "^5.4" "symfony/cache": "^5.4"
}, },
"require-dev": { "require-dev": {
"roave/security-advisories": "dev-latest" "roave/security-advisories": "dev-latest",
"phpstan/phpstan": "^2.1"
} }
} }

View File

@@ -4,7 +4,7 @@
"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": "329d7196ee1db3f0f4e8b3552e28f83a", "content-hash": "119f24aafb5ae0828f70c3c57fa07234",
"packages": [ "packages": [
{ {
"name": "graham-campbell/result-type", "name": "graham-campbell/result-type",
@@ -1733,6 +1733,64 @@
} }
], ],
"packages-dev": [ "packages-dev": [
{
"name": "phpstan/phpstan",
"version": "2.1.22",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4",
"reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0"
},
"conflict": {
"phpstan/phpstan-shim": "*"
},
"bin": [
"phpstan",
"phpstan.phar"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHPStan - PHP Static Analysis Tool",
"keywords": [
"dev",
"static analysis"
],
"support": {
"docs": "https://phpstan.org/user-guide/getting-started",
"forum": "https://github.com/phpstan/phpstan/discussions",
"issues": "https://github.com/phpstan/phpstan/issues",
"security": "https://github.com/phpstan/phpstan/security/policy",
"source": "https://github.com/phpstan/phpstan-src"
},
"funding": [
{
"url": "https://github.com/ondrejmirtes",
"type": "github"
},
{
"url": "https://github.com/phpstan",
"type": "github"
}
],
"time": "2025-08-04T19:17:37+00:00"
},
{ {
"name": "roave/security-advisories", "name": "roave/security-advisories",
"version": "dev-latest", "version": "dev-latest",

View File

@@ -1,15 +1,13 @@
<?php <?php
declare(strict_types=1);
namespace App\Handlers; namespace App\Handlers;
use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Http\JsonResponse; use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface; 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 Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Contracts\Cache\ItemInterface;
class CategoriesHandler class CategoriesHandler
{ {
@@ -18,45 +16,41 @@ class CategoriesHandler
private Builder $queryBuilder; private Builder $queryBuilder;
private ImageToolInterface $ocImageTool; private ImageToolInterface $ocImageTool;
public function __construct(Builder $queryBuilder, ImageToolInterface $ocImageTool, Settings $settings) public function __construct(Builder $queryBuilder, ImageToolInterface $ocImageTool)
{ {
$this->queryBuilder = $queryBuilder; $this->queryBuilder = $queryBuilder;
$this->ocImageTool = $ocImageTool; $this->ocImageTool = $ocImageTool;
$this->settings = $settings;
} }
public function index(): JsonResponse public function index(): JsonResponse
{ {
$cache = new FilesystemAdapter(); $languageId = 1;
$categories = $cache->get('main-page-categories', function (ItemInterface $item) { $categoriesFlat = $this->queryBuilder->newQuery()
$item->expiresAfter($this->settings->get('cache_categories_main', 60 * 5)); ->select([
$languageId = 1; 'categories.category_id' => 'id',
'categories.parent_id' => 'parent_id',
'categories.image' => 'image',
'descriptions.name' => 'name',
'descriptions.description' => 'description',
])
->from(db_table('category'), 'categories')
->join(
db_table('category_description') . ' AS descriptions',
function (JoinClause $join) use ($languageId) {
$join->on('categories.category_id', '=', 'descriptions.category_id')
->where('descriptions.language_id', '=', $languageId);
}
)
->where('categories.status', '=', 1)
->orderBy('parent_id')
->orderBy('sort_order')
->get();
$categoriesFlat = $this->queryBuilder->newQuery() $categories = $this->buildCategoryTree($categoriesFlat);
->select([
'categories.category_id' => 'id',
'categories.parent_id' => 'parent_id',
'categories.image' => 'image',
'descriptions.name' => 'name',
'descriptions.description' => 'description',
])
->from(db_table('category'), 'categories')
->join(
db_table('category_description') . ' AS descriptions',
function (JoinClause $join) use ($languageId) {
$join->on('categories.category_id', '=', 'descriptions.category_id')
->where('descriptions.language_id', '=', $languageId);
}
)
->where('categories.status', '=', 1)
->orderBy('parent_id')
->orderBy('sort_order')
->get();
$categories = $this->buildCategoryTree($categoriesFlat); return new JsonResponse([
'data' => array_map(static function ($category) {
return array_map(static function ($category) {
return [ return [
'id' => (int)$category['id'], 'id' => (int)$category['id'],
'image' => $category['image'], 'image' => $category['image'],
@@ -64,11 +58,7 @@ class CategoriesHandler
'description' => $category['description'], 'description' => $category['description'],
'children' => $category['children'], 'children' => $category['children'],
]; ];
}, $categories); }, $categories),
});
return new JsonResponse([
'data' => $categories,
]); ]);
} }

View File

@@ -1,191 +1,32 @@
<?php <?php
declare(strict_types=1);
namespace App\Handlers; namespace App\Handlers;
use App\Adapters\OcModelCatalogProductAdapter; use App\Services\ProductsService;
use App\Decorators\OcRegistryDecorator; use Exception;
use Cart\Currency;
use Cart\Tax;
use Openguru\OpenCartFramework\Config\Settings; 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\Logger\Logger;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface; use RuntimeException;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Support\PaginationHelper;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Contracts\Cache\ItemInterface;
class ProductsHandler class ProductsHandler
{ {
private Builder $queryBuilder;
private Currency $currency;
private Tax $tax;
private Settings $settings; private Settings $settings;
private OcModelCatalogProductAdapter $ocModelCatalogProduct; private ProductsService $productsService;
private ImageToolInterface $ocImageTool; private Logger $logger;
private OcRegistryDecorator $oc;
public function __construct( public function __construct(Settings $settings, ProductsService $productsService, Logger $logger)
Builder $queryBuilder,
Currency $currency,
Tax $tax,
Settings $settings,
OcModelCatalogProductAdapter $ocModelCatalogProduct,
ImageToolInterface $ocImageTool,
OcRegistryDecorator $registry
) {
$this->queryBuilder = $queryBuilder;
$this->currency = $currency;
$this->tax = $tax;
$this->settings = $settings;
$this->ocModelCatalogProduct = $ocModelCatalogProduct;
$this->ocImageTool = $ocImageTool;
$this->oc = $registry;
}
private function getProductsResponse(array $params): array
{ {
$page = $params['page']; $this->settings = $settings;
$perPage = $params['perPage']; $this->productsService = $productsService;
$categoryId = $params['categoryId']; $this->logger = $logger;
$search = $params['search'];
$forMainPage = $params['forMainPage'];
$featuredProducts = $params['featuredProducts'];
$mainpageProducts = $params['mainpageProducts'];
$languageId = 1;
$categoryName = '';
$imageWidth = 300;
$imageHeight = 300;
if ($categoryId) {
$categoryName = $this->queryBuilder->newQuery()
->select(['name'])
->from(db_table('category_description'), 'category')
->where('language_id', '=', $languageId)
->where('category_id', '=', $categoryId)
->value('name');
}
$productsQuery = $this->queryBuilder->newQuery()
->select([
'products.product_id' => 'product_id',
'products.quantity' => 'product_quantity',
'product_description.name' => 'product_name',
'products.price' => 'price',
'products.image' => 'product_image',
'products.tax_class_id' => 'tax_class_id',
])
->from(db_table('product'), 'products')
->join(
db_table('product_description') . ' AS product_description',
function (JoinClause $join) use ($languageId) {
$join->on('products.product_id', '=', 'product_description.product_id')
->where('product_description.language_id', '=', $languageId);
}
)
->when($categoryId !== 0, function (Builder $query) use ($categoryId) {
$query->join(
db_table('product_to_category') . ' AS product_to_category',
function (JoinClause $join) use ($categoryId) {
$join->on('product_to_category.product_id', '=', 'products.product_id')
->where('product_to_category.category_id', '=', $categoryId);
}
);
})
->when(
$forMainPage && $mainpageProducts === 'featured' && $featuredProducts,
function (Builder $query) use ($featuredProducts) {
$query->whereIn('products.product_id', $featuredProducts);
}
)
->when($search, function (Builder $query) use ($search) {
$query->where('product_description.name', 'LIKE', '%' . $search . '%');
});
$total = $productsQuery->count();
$lastPage = PaginationHelper::calculateLastPage($total, $perPage);
$hasMore = $page + 1 <= $lastPage;
$products = $productsQuery
->forPage($page, $perPage)
->orderBy($mainpageProducts === 'latest' ? 'date_modified' : 'viewed', 'DESC')
->get();
$productIds = Arr::pluck($products, 'product_id');
$productsImages = [];
if ($productIds) {
$productsImages = $this->queryBuilder->newQuery()
->select([
'products_images.product_id' => 'product_id',
'products_images.image' => 'image',
])
->from(db_table('product_image'), 'products_images')
->orderBy('products_images.sort_order', 'ASC')
->whereIn('product_id', $productIds)
->get();
}
$productsImagesMap = [];
foreach ($productsImages as $item) {
$productsImagesMap[$item['product_id']][] = [
'url' => $this->ocImageTool->resize($item['image'], $imageWidth, $imageHeight, 'placeholder.png'),
'alt' => 'Product Image',
];
}
return [
'data' => array_map(function ($product) use ($productsImagesMap, $imageWidth, $imageHeight) {
$allImages = [];
$image = $this->ocImageTool->resize(
$product['product_image'],
$imageWidth,
$imageHeight,
'placeholder.png'
);
$allImages[] = [
'url' => $image,
'alt' => $product['product_name'],
];
$price = $this->currency->format(
$this->tax->calculate(
$product['price'],
$product['tax_class_id'],
$this->settings->get('oc_config_tax'),
),
$this->settings->get('oc_default_currency'),
);
if (! empty($productsImagesMap[$product['product_id']])) {
$allImages = array_merge($allImages, $productsImagesMap[$product['product_id']]);
}
return [
'id' => (int) $product['product_id'],
'product_quantity' => (int) $product['product_quantity'],
'name' => $product['product_name'],
'price' => $price,
'images' => $allImages,
];
}, $products),
'meta' => [
'currentCategoryName' => $categoryName,
'hasMore' => $hasMore,
]
];
} }
public function handle(Request $request): JsonResponse public function handle(Request $request): JsonResponse
{ {
$cache = new FilesystemAdapter();
$page = (int) $request->get('page', 1); $page = (int) $request->get('page', 1);
$perPage = min((int) $request->get('perPage', 6), 15); $perPage = min((int) $request->get('perPage', 6), 15);
$categoryId = (int) $request->get('categoryId', 0); $categoryId = (int) $request->get('categoryId', 0);
@@ -194,45 +35,17 @@ class ProductsHandler
$featuredProducts = $this->settings->get('featured_products'); $featuredProducts = $this->settings->get('featured_products');
$mainpageProducts = $this->settings->get('mainpage_products'); $mainpageProducts = $this->settings->get('mainpage_products');
if ($forMainPage && $page === 1) { $response = $this->productsService->getProductsResponse(
$response = $cache->get( compact(
'main-page-products', 'page',
function (ItemInterface $cacheitem) use ( 'perPage',
$page, 'categoryId',
$perPage, 'search',
$categoryId, 'forMainPage',
$search, 'featuredProducts',
$forMainPage, 'mainpageProducts'
$featuredProducts, )
$mainpageProducts );
): array {
$cacheitem->expiresAfter($this->settings->get('cache_products_main', 60 * 10));
return $this->getProductsResponse(
compact(
'page',
'perPage',
'categoryId',
'search',
'forMainPage',
'featuredProducts',
'mainpageProducts'
)
);
}
);
} else {
$response = $this->getProductsResponse(
compact(
'page',
'perPage',
'categoryId',
'search',
'forMainPage',
'featuredProducts',
'mainpageProducts'
)
);
}
return new JsonResponse($response); return new JsonResponse($response);
} }
@@ -246,152 +59,22 @@ class ProductsHandler
$imageFullWidth = 1000; $imageFullWidth = 1000;
$imageFullHeight = 1000; $imageFullHeight = 1000;
$product = $this->queryBuilder->newQuery() try {
->select([ $product = $this->productsService->getProduct(
'products.product_id' => 'product_id', $productId,
'product_description.name' => 'product_name', $languageId,
'product_description.description' => 'product_description', $imageWidth,
'products.price' => 'price', $imageHeight,
'products.minimum' => 'minimum', $imageFullWidth,
'products.quantity' => 'quantity', $imageFullHeight
'products.image' => 'product_image', );
'products.tax_class_id' => 'tax_class_id', } catch (Exception $exception) {
'manufacturer.name' => 'product_manufacturer', $this->logger->logException($exception);
]) throw new RuntimeException('Error get product with id ' . $productId);
->from(db_table('product'), 'products')
->join(
db_table('product_description') . ' AS product_description',
function (JoinClause $join) use ($languageId) {
$join->on('products.product_id', '=', 'product_description.product_id')
->where('product_description.language_id', '=', $languageId);
}
)
->leftJoin(
db_table('manufacturer') . ' AS manufacturer',
function (JoinClause $join) use ($languageId) {
$join->on('products.manufacturer_id', '=', 'manufacturer.manufacturer_id');
}
)
->where('products.product_id', '=', $productId)
->limit(1)
->firstOrNull();
if (! $product) {
return new JsonResponse([], Response::HTTP_NOT_FOUND);
} }
$productsImages = $this->queryBuilder->newQuery()
->select([
'products_images.product_id' => 'product_id',
'products_images.image' => 'image',
])
->from(db_table('product_image'), 'products_images')
->orderBy('products_images.sort_order', 'ASC')
->where('products_images.product_id', '=', $productId)
->get();
$imagePaths = [];
$imagePaths[] = $product['product_image'];
foreach ($productsImages as $item) {
$imagePaths[] = $item['image'];
}
$images = [];
foreach ($imagePaths as $imagePath) {
[$width, $height] = $this->ocImageTool->getRealSize($imagePath);
$images[] = [
'thumbnailURL' => $this->ocImageTool->resize($imagePath, $imageWidth, $imageHeight, 'placeholder.png'),
'largeURL' => $this->ocImageTool->resize($imagePath, $imageFullWidth, $imageFullHeight, 'placeholder.png'),
'width' => $width,
'height' => $height,
'alt' => $product['product_name'],
];
}
$price = $this->currency->format(
$this->tax->calculate(
$product['price'],
$product['tax_class_id'],
$this->settings->get('oc_config_tax'),
),
$this->settings->get('oc_default_currency'),
);
$data = [
'id' => $product['product_id'],
'name' => $product['product_name'],
'description' => html_entity_decode($product['product_description']),
'manufacturer' => $product['product_manufacturer'],
'price' => $price,
'minimum' => $product['minimum'],
'quantity' => $product['quantity'],
'images' => $images,
'options' => $this->loadProductOptions($product),
'attributes' => $this->loadProductAttributes($product['product_id']),
];
return new JsonResponse([ return new JsonResponse([
'data' => $data, 'data' => $product,
]); ]);
} }
private function loadProductOptions($product): array
{
$result = [];
$productId = $product['product_id'];
$taxClassId = $product['tax_class_id'];
$options = $this->ocModelCatalogProduct->getProductOptions($productId);
$ocConfigTax = $this->settings->get('oc_config_tax');
$ocDefaultCurrency = $this->settings->get('oc_default_currency');
foreach ($options as $option) {
$product_option_value_data = [];
foreach ($option['product_option_value'] as $option_value) {
if (! $option_value['subtract'] || ($option_value['quantity'] > 0)) {
if ((float) $option_value['price']) {
$priceWithTax = $this->tax->calculate(
$option_value['price'],
$taxClassId,
$ocConfigTax ? 'Р' : false,
);
$price = $this->currency->format($priceWithTax, $ocDefaultCurrency);
} else {
$price = false;
}
$product_option_value_data[] = [
'product_option_value_id' => (int) $option_value['product_option_value_id'],
'option_value_id' => (int) $option_value['option_value_id'],
'name' => $option_value['name'],
'image' => $this->ocImageTool->resize($option_value['image'], 50, 50),
'price' => $price,
'price_prefix' => $option_value['price_prefix'],
'selected' => false,
];
}
}
$result[] = [
'product_option_id' => (int) $option['product_option_id'],
'values' => $product_option_value_data,
'option_id' => (int) $option['option_id'],
'name' => $option['name'],
'type' => $option['type'],
'value' => $option['value'],
'required' => filter_var($option['required'], FILTER_VALIDATE_BOOLEAN),
];
}
return $result;
}
private function loadProductAttributes(int $productId): array
{
$this->oc->load->model('catalog/product');
return $this->oc->model_catalog_product->getProductAttributes($productId);
}
} }

View File

@@ -0,0 +1,344 @@
<?php
namespace App\Services;
use App\Adapters\OcModelCatalogProductAdapter;
use App\Decorators\OcRegistryDecorator;
use Cart\Currency;
use Cart\Tax;
use Exception;
use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Support\PaginationHelper;
class ProductsService
{
private Builder $queryBuilder;
private Currency $currency;
private Tax $tax;
private Settings $settings;
private OcModelCatalogProductAdapter $ocModelCatalogProduct;
private ImageToolInterface $ocImageTool;
private OcRegistryDecorator $oc;
public function __construct(
Builder $queryBuilder,
Currency $currency,
Tax $tax,
Settings $settings,
OcModelCatalogProductAdapter $ocModelCatalogProduct,
ImageToolInterface $ocImageTool,
OcRegistryDecorator $registry
) {
$this->queryBuilder = $queryBuilder;
$this->currency = $currency;
$this->tax = $tax;
$this->settings = $settings;
$this->ocModelCatalogProduct = $ocModelCatalogProduct;
$this->ocImageTool = $ocImageTool;
$this->oc = $registry;
}
public function getProductsResponse(array $params): array
{
$page = $params['page'];
$perPage = $params['perPage'];
$categoryId = $params['categoryId'];
$search = $params['search'];
$forMainPage = $params['forMainPage'];
$featuredProducts = $params['featuredProducts'];
$mainpageProducts = $params['mainpageProducts'];
$languageId = 1;
$categoryName = '';
$imageWidth = 300;
$imageHeight = 300;
if ($categoryId) {
$categoryName = $this->queryBuilder->newQuery()
->select(['name'])
->from(db_table('category_description'), 'category')
->where('language_id', '=', $languageId)
->where('category_id', '=', $categoryId)
->value('name');
}
$productsQuery = $this->queryBuilder->newQuery()
->select([
'products.product_id' => 'product_id',
'products.quantity' => 'product_quantity',
'product_description.name' => 'product_name',
'products.price' => 'price',
'products.image' => 'product_image',
'products.tax_class_id' => 'tax_class_id',
])
->from(db_table('product'), 'products')
->join(
db_table('product_description') . ' AS product_description',
function (JoinClause $join) use ($languageId) {
$join->on('products.product_id', '=', 'product_description.product_id')
->where('product_description.language_id', '=', $languageId);
}
)
->when($categoryId !== 0, function (Builder $query) use ($categoryId) {
$query->join(
db_table('product_to_category') . ' AS product_to_category',
function (JoinClause $join) use ($categoryId) {
$join->on('product_to_category.product_id', '=', 'products.product_id')
->where('product_to_category.category_id', '=', $categoryId);
}
);
})
->when(
$forMainPage && $mainpageProducts === 'featured' && $featuredProducts,
function (Builder $query) use ($featuredProducts) {
$query->whereIn('products.product_id', $featuredProducts);
}
)
->when($search, function (Builder $query) use ($search) {
$query->where('product_description.name', 'LIKE', '%' . $search . '%');
});
$total = $productsQuery->count();
$lastPage = PaginationHelper::calculateLastPage($total, $perPage);
$hasMore = $page + 1 <= $lastPage;
$products = $productsQuery
->forPage($page, $perPage)
->orderBy($mainpageProducts === 'latest' ? 'date_modified' : 'viewed', 'DESC')
->get();
$productIds = Arr::pluck($products, 'product_id');
$productsImages = [];
if ($productIds) {
$productsImages = $this->queryBuilder->newQuery()
->select([
'products_images.product_id' => 'product_id',
'products_images.image' => 'image',
])
->from(db_table('product_image'), 'products_images')
->orderBy('products_images.sort_order')
->whereIn('product_id', $productIds)
->get();
}
$productsImagesMap = [];
foreach ($productsImages as $item) {
$productsImagesMap[$item['product_id']][] = [
'url' => $this->ocImageTool->resize($item['image'], $imageWidth, $imageHeight, 'placeholder.png'),
'alt' => 'Product Image',
];
}
return [
'data' => array_map(function ($product) use ($productsImagesMap, $imageWidth, $imageHeight) {
$allImages = [];
$image = $this->ocImageTool->resize(
$product['product_image'],
$imageWidth,
$imageHeight,
'placeholder.png'
);
$allImages[] = [
'url' => $image,
'alt' => $product['product_name'],
];
$price = $this->currency->format(
$this->tax->calculate(
$product['price'],
$product['tax_class_id'],
$this->settings->get('oc_config_tax'),
),
$this->settings->get('oc_default_currency'),
);
if (! empty($productsImagesMap[$product['product_id']])) {
$allImages = array_merge($allImages, $productsImagesMap[$product['product_id']]);
}
return [
'id' => (int) $product['product_id'],
'product_quantity' => (int) $product['product_quantity'],
'name' => $product['product_name'],
'price' => $price,
'images' => $allImages,
];
}, $products),
'meta' => [
'currentCategoryName' => $categoryName,
'hasMore' => $hasMore,
]
];
}
/**
* @throws Exception
*/
public function getProduct(
int $productId,
int $languageId,
int $imageWidth,
int $imageHeight,
int $imageFullWidth,
int $imageFullHeight
): array {
$product = $this->queryBuilder->newQuery()
->select([
'products.product_id' => 'product_id',
'product_description.name' => 'product_name',
'product_description.description' => 'product_description',
'products.price' => 'price',
'products.minimum' => 'minimum',
'products.quantity' => 'quantity',
'products.image' => 'product_image',
'products.tax_class_id' => 'tax_class_id',
'manufacturer.name' => 'product_manufacturer',
])
->from(db_table('product'), 'products')
->join(
db_table('product_description') . ' AS product_description',
function (JoinClause $join) use ($languageId) {
$join->on('products.product_id', '=', 'product_description.product_id')
->where('product_description.language_id', '=', $languageId);
}
)
->leftJoin(
db_table('manufacturer') . ' AS manufacturer',
function (JoinClause $join) {
$join->on('products.manufacturer_id', '=', 'manufacturer.manufacturer_id');
}
)
->where('products.product_id', '=', $productId)
->limit(1)
->firstOrNull();
if (! $product) {
return [];
}
$productsImages = $this->queryBuilder->newQuery()
->select([
'products_images.product_id' => 'product_id',
'products_images.image' => 'image',
])
->from(db_table('product_image'), 'products_images')
->orderBy('products_images.sort_order')
->where('products_images.product_id', '=', $productId)
->get();
$imagePaths = [];
$imagePaths[] = $product['product_image'];
foreach ($productsImages as $item) {
$imagePaths[] = $item['image'];
}
$images = [];
foreach ($imagePaths as $imagePath) {
[$width, $height] = $this->ocImageTool->getRealSize($imagePath);
$images[] = [
'thumbnailURL' => $this->ocImageTool->resize($imagePath, $imageWidth, $imageHeight, 'placeholder.png'),
'largeURL' => $this->ocImageTool->resize(
$imagePath,
$imageFullWidth,
$imageFullHeight,
'placeholder.png'
),
'width' => $width,
'height' => $height,
'alt' => $product['product_name'],
];
}
$price = $this->currency->format(
$this->tax->calculate(
$product['price'],
$product['tax_class_id'],
$this->settings->get('oc_config_tax'),
),
$this->settings->get('oc_default_currency'),
);
return [
'id' => $product['product_id'],
'name' => $product['product_name'],
'description' => html_entity_decode($product['product_description']),
'manufacturer' => $product['product_manufacturer'],
'price' => $price,
'minimum' => $product['minimum'],
'quantity' => $product['quantity'],
'images' => $images,
'options' => $this->loadProductOptions($product),
'attributes' => $this->loadProductAttributes($product['product_id']),
];
}
private function loadProductOptions($product): array
{
$result = [];
$productId = $product['product_id'];
$taxClassId = $product['tax_class_id'];
$options = $this->ocModelCatalogProduct->getProductOptions($productId);
$ocConfigTax = $this->settings->get('oc_config_tax');
$ocDefaultCurrency = $this->settings->get('oc_default_currency');
foreach ($options as $option) {
$product_option_value_data = [];
foreach ($option['product_option_value'] as $option_value) {
if (! $option_value['subtract'] || ($option_value['quantity'] > 0)) {
if ((float) $option_value['price']) {
$priceWithTax = $this->tax->calculate(
$option_value['price'],
$taxClassId,
$ocConfigTax ? 'Р' : false,
);
$price = $this->currency->format($priceWithTax, $ocDefaultCurrency);
} else {
$price = false;
}
$product_option_value_data[] = [
'product_option_value_id' => (int) $option_value['product_option_value_id'],
'option_value_id' => (int) $option_value['option_value_id'],
'name' => $option_value['name'],
'image' => $this->ocImageTool->resize($option_value['image'], 50, 50),
'price' => $price,
'price_prefix' => $option_value['price_prefix'],
'selected' => false,
];
}
}
$result[] = [
'product_option_id' => (int) $option['product_option_id'],
'values' => $product_option_value_data,
'option_id' => (int) $option['option_id'],
'name' => $option['name'],
'type' => $option['type'],
'value' => $option['value'],
'required' => filter_var($option['required'], FILTER_VALIDATE_BOOLEAN),
];
}
return $result;
}
/**
* @throws Exception
*/
private function loadProductAttributes(int $productId): array
{
$this->oc->load->model('catalog/product');
return $this->oc->model_catalog_product->getProductAttributes($productId);
}
}