queryBuilder = $queryBuilder; $this->currency = $currency; $this->tax = $tax; $this->settings = $settings; $this->image = $image; $this->oc = $registry; $this->logger = $logger; $this->criteriaBuilder = $criteriaBuilder; $this->priceCalculator = $priceCalculator; } /** * @throws ImageNotFoundException */ public function getProductsResponse(array $params, int $languageId, int $storeId): array { $page = $params['page']; $perPage = $params['perPage']; $search = $params['search'] ?? false; $categoryName = ''; $maxPages = 200; $filters = $params['filters'] ?? []; $aspectRatio = $this->settings->get('app.image_aspect_ratio', '1:1'); $cropAlgorithm = $this->settings->get('app.image_crop_algorithm', 'cover'); [$imageWidth, $imageHeight] = ImageUtils::aspectRatioToSize($aspectRatio); $customerGroupId = $this->settings->config()->getOrders()->getOcCustomerGroupId(); $currency = $this->settings->config()->getStore()->getOcDefaultCurrency(); $specialPriceSql = "(SELECT price FROM oc_product_special ps WHERE ps.product_id = products.product_id AND ps.customer_group_id = $customerGroupId AND ((ps.date_start = '0000-00-00' OR ps.date_start < NOW()) AND (ps.date_end = '0000-00-00' OR ps.date_end > NOW())) ORDER BY ps.priority ASC, ps.price ASC LIMIT 1) AS special"; $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', 'manufacturer.name' => 'manufacturer_name', 'category_description.name' => 'category_name', new RawExpression($specialPriceSql), ]) ->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); } ) ->join( new Table(db_table('product_to_store'), 'product_to_store'), function (JoinClause $join) use ($storeId) { $join->on('product_to_store.product_id', '=', 'products.product_id') ->where('product_to_store.store_id', '=', $storeId); } ) ->leftJoin(new Table(db_table('manufacturer'), 'manufacturer'), function (JoinClause $join) { $join->on('products.manufacturer_id', '=', 'manufacturer.manufacturer_id'); }) ->leftJoin(new Table(db_table('product_to_category'), 'product_to_category'), function (JoinClause $join) { $join->on('products.product_id', '=', 'product_to_category.product_id') ->where('product_to_category.main_category', '=', 1); }) ->leftJoin( new Table(db_table('category_description'), 'category_description'), function (JoinClause $join) use ($languageId) { $join->on('product_to_category.category_id', '=', 'category_description.category_id') ->where('category_description.language_id', '=', $languageId); } ) ->where('products.status', '=', 1) ->whereRaw('products.date_available < NOW()') ->when($search, function (Builder $query) use ($search) { $query->where('product_description.name', 'LIKE', '%' . $search . '%'); }); $this->criteriaBuilder->apply($productsQuery, $filters); $total = $productsQuery->count(); $lastPage = min(PaginationHelper::calculateLastPage($total, $perPage), $maxPages); $hasMore = $page + 1 <= $lastPage; $products = $productsQuery ->forPage($page, $perPage) ->orderBy('date_modified', '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(); } $span = SentryService::startSpan('crop_images', 'image.process'); $productsImagesMap = []; foreach ($productsImages as $item) { $productId = $item['product_id']; // Ограничиваем количество картинок для каждого товара до 3 if (! isset($productsImagesMap[$productId])) { $productsImagesMap[$productId] = []; } if (count($productsImagesMap[$productId]) < 2) { $productsImagesMap[$productId][] = [ 'url' => $this->image->make($item['image'])->crop($cropAlgorithm, $imageWidth, $imageHeight)->url(), 'alt' => 'Product Image', ]; } } SentryService::endSpan($span); $debug = []; if (env('APP_DEBUG')) { $debug = [ 'sql' => $productsQuery->toRawSql(), ]; } return [ 'data' => array_map( function ($product) use ($productsImagesMap, $cropAlgorithm, $imageWidth, $imageHeight, $currency) { $allImages = []; $image = $this->image->make($product['product_image'], false) ->crop($cropAlgorithm, $imageWidth, $imageHeight) ->url(); $allImages[] = [ 'url' => $image, 'alt' => Str::htmlEntityEncode($product['product_name']), ]; $price = $this->priceCalculator->format($product['price'], $product['tax_class_id']); $priceNumeric = $this->priceCalculator->getPriceNumeric( $product['price'], $product['tax_class_id'] ); $special = false; $specialPriceNumeric = null; if ($product['special'] && (float) $product['special'] >= 0) { $specialPriceNumeric = $this->tax->calculate( $product['special'], $product['tax_class_id'], $this->settings->config()->getStore()->isOcConfigTax(), ); $special = $this->currency->format( $specialPriceNumeric, $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' => Str::htmlEntityEncode($product['product_name']), 'price' => $price, 'special' => $special, 'image' => $image, 'images' => $allImages, 'special_numeric' => $specialPriceNumeric, 'price_numeric' => $priceNumeric, 'final_price_numeric' => $specialPriceNumeric ?: $priceNumeric, 'manufacturer_name' => $product['manufacturer_name'], 'category_name' => $product['category_name'], ]; }, $products ), 'meta' => [ 'currentCategoryName' => $categoryName, 'hasMore' => $hasMore, 'debug' => $debug, 'total' => $total, ] ]; } /** * @throws EntityNotFoundException * @throws Exception */ public function getProductById(int $productId): array { $this->oc->load->language('product/product'); $this->oc->load->model('catalog/category'); $this->oc->load->model('catalog/manufacturer'); $this->oc->load->model('catalog/product'); $this->oc->load->model('catalog/review'); $this->oc->load->model('tool/image'); $configTax = $this->oc->config->get('config_tax'); $product_info = $this->oc->model_catalog_product->getProduct($productId); $currency = $this->oc->session->data['currency']; if (! $product_info) { throw new EntityNotFoundException('Product with id ' . $productId . ' not found'); } $data = []; $data['text_minimum'] = sprintf($this->oc->language->get('text_minimum'), $product_info['minimum']); $data['tab_review'] = sprintf($this->oc->language->get('tab_review'), $product_info['reviews']); $data['product_id'] = $productId; $data['name'] = Str::htmlEntityEncode($product_info['name']); $data['manufacturer'] = $product_info['manufacturer']; $data['model'] = $product_info['model']; $data['reward'] = $product_info['reward']; $data['points'] = (int) $product_info['points']; $data['description'] = Str::htmlEntityEncode($product_info['description']); $data['share'] = Str::htmlEntityEncode( $this->oc->url->link('product/product', [ 'product_id' => $productId, 'utm_source' => 'megapay', 'utm_medium' => 'telegram', 'utm_campaign' => 'product_click', 'utm_content' => 'product_button', ]), ); if ($product_info['quantity'] <= 0) { $data['stock'] = $product_info['stock_status']; } elseif ($this->oc->config->get('config_stock_display')) { $data['stock'] = $product_info['quantity']; } else { $data['stock'] = $this->oc->language->get('text_instock'); } $data['images'] = []; $price = $this->priceCalculator->format($product_info['price'], $product_info['tax_class_id']); $priceNumeric = $this->priceCalculator->getPriceNumeric($product_info['price'], $product_info['tax_class_id']); $data['price'] = $price; $data['currency'] = $currency; $data['final_price_numeric'] = $priceNumeric; if (! is_null($product_info['special']) && (float) $product_info['special'] >= 0) { $productSpecialPrice = $this->tax->calculate( $product_info['special'], $product_info['tax_class_id'], $configTax, ); $data['special'] = $this->currency->format($productSpecialPrice, $currency); $data['final_price_numeric'] = $productSpecialPrice; $tax_price = (float) $product_info['special']; } else { $data['special'] = false; $tax_price = (float) $product_info['price']; } if ($configTax) { $data['tax'] = $this->currency->format($tax_price, $currency); } else { $data['tax'] = false; } $discounts = $this->oc->model_catalog_product->getProductDiscounts($productId); $data['discounts'] = []; foreach ($discounts as $discount) { $data['discounts'][] = array( 'quantity' => $discount['quantity'], 'price' => $this->currency->format( $this->tax->calculate( $discount['price'], $product_info['tax_class_id'], $configTax, ), $currency ) ); } $data['options'] = []; foreach ($this->oc->model_catalog_product->getProductOptions($productId) as $option) { $product_option_value_data = []; foreach ($option['product_option_value'] as $option_value) { if (! $option_value['subtract'] || ($option_value['quantity'] > 0)) { $price = $this->currency->format( $this->tax->calculate( $option_value['price'], $product_info['tax_class_id'], $configTax ? 'P' : false ), $currency ); $product_option_value_data[] = array( '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->oc->model_tool_image->resize($option_value['image'], 50, 50), 'price' => $price, 'price_prefix' => $option_value['price_prefix'], 'selected' => false, ); } } $data['options'][] = array( 'product_option_id' => $option['product_option_id'], 'product_option_value' => $product_option_value_data, 'option_id' => $option['option_id'], 'name' => $option['name'], 'type' => $option['type'], 'value' => $option['value'], 'required' => filter_var($option['required'], FILTER_VALIDATE_BOOLEAN), ); } if ($product_info['minimum']) { $data['minimum'] = (int) $product_info['minimum']; } else { $data['minimum'] = 1; } $data['review_status'] = $this->oc->config->get('config_review_status'); $data['review_guest'] = true; $data['customer_name'] = 'John Doe'; $data['reviews'] = sprintf($this->oc->language->get('text_reviews'), (int) $product_info['reviews']); $data['rating'] = (int) $product_info['rating']; $data['attribute_groups'] = $this->oc->model_catalog_product->getProductAttributes($productId); $data['tags'] = array(); if ($product_info['tag']) { $tags = explode(',', $product_info['tag']); foreach ($tags as $tag) { $data['tags'][] = array( 'tag' => trim($tag), 'href' => $this->oc->url->link('product/search', 'tag=' . trim($tag)) ); } } $data['recurrings'] = $this->oc->model_catalog_product->getProfiles($productId); $data['category'] = $this->getProductMainCategory($productId); $data['id'] = $productId; $this->oc->model_catalog_product->updateViewed($productId); return $data; } private function getProductMainCategory(int $productId): ?array { return $this->queryBuilder->newQuery() ->select([ 'category_description.category_id' => 'id', 'category_description.name' => 'name', ]) ->from(db_table('category_description'), 'category_description') ->join(new Table(db_table('product_to_category'), 'product_to_category'), function (JoinClause $join) { $join->on('product_to_category.category_id', '=', 'category_description.category_id') ->where('product_to_category.main_category', '=', 1); }) ->where('product_to_category.product_id', '=', $productId) ->firstOrNull(); } public function getProductImages(int $productId): array { $aspectRatio = $this->settings->get('app.image_aspect_ratio', '1:1'); $cropAlgorithm = $this->settings->get('app.image_crop_algorithm', 'cover'); [$imageWidth, $imageHeight] = ImageUtils::aspectRatioToSize($aspectRatio); $imageFullWidth = 1000; $imageFullHeight = 1000; $product_info = $this->oc->model_catalog_product->getProduct($productId); if (! $product_info) { throw new EntityNotFoundException('Product with id ' . $productId . ' not found'); } $allImages = []; if ($product_info['image']) { $allImages[] = $product_info['image']; } $results = $this->oc->model_catalog_product->getProductImages($productId); foreach ($results as $result) { $allImages[] = $result['image']; } $images = []; foreach ($allImages as $imagePath) { try { [$width, $height] = $this->image->make($imagePath)->getRealSize(); $images[] = [ 'thumbnailURL' => $this->image->make($imagePath) ->crop($cropAlgorithm, $imageWidth, $imageHeight) ->url(), 'largeURL' => $this->image->make($imagePath)->resize($imageFullWidth, $imageFullHeight)->url(), 'width' => $width, 'height' => $height, 'alt' => Str::htmlEntityEncode($product_info['name']), ]; } catch (Exception $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); } } return $images; } }