diff --git a/Makefile b/Makefile index 808d5eb..ee4e94d 100644 --- a/Makefile +++ b/Makefile @@ -29,4 +29,7 @@ link: dev: $(MAKE) link && \ - cd spa && npm run dev \ No newline at end of file + 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" \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index a7fdf7a..25f18a7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -12,7 +12,7 @@ services: deploy: resources: limits: - memory: 256M + memory: 512M environment: - WEB_DOCUMENT_ROOT=/web/upload - PHP_DISPLAY_ERRORS=1 diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/composer.json b/module/oc_telegram_shop/upload/oc_telegram_shop/composer.json index 57dbf32..afe9a5f 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/composer.json +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/composer.json @@ -27,6 +27,7 @@ "symfony/cache": "^5.4" }, "require-dev": { - "roave/security-advisories": "dev-latest" + "roave/security-advisories": "dev-latest", + "phpstan/phpstan": "^2.1" } } diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/composer.lock b/module/oc_telegram_shop/upload/oc_telegram_shop/composer.lock index 3686230..a704d07 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/composer.lock +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "329d7196ee1db3f0f4e8b3552e28f83a", + "content-hash": "119f24aafb5ae0828f70c3c57fa07234", "packages": [ { "name": "graham-campbell/result-type", @@ -1733,6 +1733,64 @@ } ], "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", "version": "dev-latest", diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/CategoriesHandler.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/CategoriesHandler.php index c2d6c7a..63b6da7 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/CategoriesHandler.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/CategoriesHandler.php @@ -1,15 +1,13 @@ queryBuilder = $queryBuilder; $this->ocImageTool = $ocImageTool; - $this->settings = $settings; } public function index(): JsonResponse { - $cache = new FilesystemAdapter(); + $languageId = 1; - $categories = $cache->get('main-page-categories', function (ItemInterface $item) { - $item->expiresAfter($this->settings->get('cache_categories_main', 60 * 5)); - $languageId = 1; + $categoriesFlat = $this->queryBuilder->newQuery() + ->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(); - $categoriesFlat = $this->queryBuilder->newQuery() - ->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); - $categories = $this->buildCategoryTree($categoriesFlat); - - return array_map(static function ($category) { + return new JsonResponse([ + 'data' => array_map(static function ($category) { return [ 'id' => (int)$category['id'], 'image' => $category['image'], @@ -64,11 +58,7 @@ class CategoriesHandler 'description' => $category['description'], 'children' => $category['children'], ]; - }, $categories); - }); - - return new JsonResponse([ - 'data' => $categories, + }, $categories), ]); } diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/ProductsHandler.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/ProductsHandler.php index 1857c24..a4ba38c 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/ProductsHandler.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/ProductsHandler.php @@ -1,191 +1,32 @@ 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 + public function __construct(Settings $settings, ProductsService $productsService, Logger $logger) { - $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', '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, - ] - ]; + $this->settings = $settings; + $this->productsService = $productsService; + $this->logger = $logger; } public function handle(Request $request): JsonResponse { - $cache = new FilesystemAdapter(); - $page = (int) $request->get('page', 1); $perPage = min((int) $request->get('perPage', 6), 15); $categoryId = (int) $request->get('categoryId', 0); @@ -194,45 +35,17 @@ class ProductsHandler $featuredProducts = $this->settings->get('featured_products'); $mainpageProducts = $this->settings->get('mainpage_products'); - if ($forMainPage && $page === 1) { - $response = $cache->get( - 'main-page-products', - function (ItemInterface $cacheitem) use ( - $page, - $perPage, - $categoryId, - $search, - $forMainPage, - $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' - ) - ); - } + $response = $this->productsService->getProductsResponse( + compact( + 'page', + 'perPage', + 'categoryId', + 'search', + 'forMainPage', + 'featuredProducts', + 'mainpageProducts' + ) + ); return new JsonResponse($response); } @@ -246,152 +59,22 @@ class ProductsHandler $imageFullWidth = 1000; $imageFullHeight = 1000; - $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) 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); + try { + $product = $this->productsService->getProduct( + $productId, + $languageId, + $imageWidth, + $imageHeight, + $imageFullWidth, + $imageFullHeight + ); + } catch (Exception $exception) { + $this->logger->logException($exception); + throw new RuntimeException('Error get product with id ' . $productId); } - $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([ - '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); - } } diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/ProductsService.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/ProductsService.php new file mode 100644 index 0000000..8dae934 --- /dev/null +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/ProductsService.php @@ -0,0 +1,344 @@ +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); + } +} \ No newline at end of file