<?php
declare(strict_types=1);
namespace App\Controller;
use App\Doctrine\ORM\ProductReadonlyRepository;
use App\Doctrine\ORM\ProductRepository;
use App\Doctrine\ORM\ProductVariantCalculationRepository;
use App\Doctrine\ORM\TileDetailsRepositoryInterface;
use App\Entity\Product\Product;
use App\Entity\Product\ProductAttribute;
use App\Entity\Taxonomy\Taxon;
use App\Twig\PriceExtension;
use Doctrine\ORM\EntityManagerInterface;
use League\Csv\Writer;
use Sylius\Bundle\MoneyBundle\Templating\Helper\ConvertMoneyHelperInterface;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Core\Model\OrderItemInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Core\Repository\OrderItemRepositoryInterface;
use Sylius\Component\Currency\Context\CurrencyContextInterface;
use Sylius\Component\Locale\Context\LocaleContextInterface;
use Sylius\Component\Locale\Provider\LocaleProviderInterface;
use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\Annotation\Route;
class ProductController extends AbstractController
{
private ?Taxon $tileTaxon = null;
public function __construct(
private LocaleContextInterface $localeContext,
private TaxonRepositoryInterface $taxonRepository,
private TileDetailsRepositoryInterface $tileDetailsRepository,
private ProductVariantCalculationRepository $productVariantCalculationRepository,
private EntityManagerInterface $entityManager,
private ProductRepository $productRepository,
private OrderItemRepositoryInterface $orderItemRepository,
private PriceExtension $priceExtension,
//private ConvertMoneyHelperInterface $convertMoneyHelper,
private ChannelContextInterface $channelContext,
private CurrencyContextInterface $currencyContext,
//private ProductReadonlyRepository $productReadonlyRepository,
) {
}
private array $finishingPiecesTaxons;
public function renderHoverPart(string|int|ProductInterface $product, Request $request): Response
{
$locale = $this->localeContext->getLocaleCode();
$cacheKey = 'product-box-hover1-'.$locale.'-'.$product;
if (is_string($product)) {
$response = apcu_fetch($cacheKey);
if ($response !== false) {
return $response;
}
}
$product = $this->getProduct($product);
/** @var ProductAttribute[] $attributes */
$attributes = $product->getAttributesByLocale($locale, 'en_US')
->slice(0, 8);
//if ($this->tileTaxon === null) {
// $this->tileTaxon = $this->taxonRepository->findOneBy(['code' => 'ALL_TILES']);
//}
$description = '';
if (!$this->hasTaxonByCode($product, 'ALL_TILES')) {
$attributes = [];
$description = $product->getShortDescription();
}
$response = $this->render('@SyliusShop/Product/boxHover.html.twig', [
'product' => $product,
'collection' => $this->getProductCollection($product),
'brand' => $product->getBrand(),
'attributes' => $attributes,
'description' => $description,
]);
return $response;
//
//$response->setPublic();
//$response->setTtl(604800);
//$response->setMaxAge(time() + 604800);
//
//apcu_store($cacheKey, $response, 3600);
//
//return $response;
}
private function hasTaxonByCode(ProductInterface $product, string $code): bool
{
foreach ($product->getTaxons() as $taxon) {
if ($taxon->getCode() === $code) {
return true;
}
}
return false;
}
public function renderBox(string|int|ProductInterface $product, Request $request): Response
{
$product = $this->getProduct($product);
$response = $this->render('@SyliusShop/Product/_box.html.twig', [
'product' => $product,
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderAttributeList(string|int|ProductInterface $product, Request $request): mixed
{
$product = $this->getProduct($product);
/** @var ProductAttribute[] $attributes */
$attributes = $product->getAttributesByLocale($this->localeContext->getLocaleCode(), 'en_US')
->slice(0, 1);
try {
$response = $this->render('@SyliusShop/Product/showAttributesList.html.twig', [
'product' => $product,
'collection' => $this->getProductCollection($product),
'brand' => $product->getBrand(),
'attributes' => $attributes,
]);
} catch (\Exception $exception) {
if (isset($_GET['hehehe'])) {
dump($exception);
exit;
}
}
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderLowestMarketPrice(string|int|ProductInterface $product, Request $request): mixed
{
$product = $this->getProduct($product);
$response = $this->render('@SyliusShop/Product/_lowestMarketPrice.html.twig', [
'product' => $product,
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderCartAttributeList(string|int|ProductInterface $product, string|int|OrderItemInterface $item): mixed
{
$product = $this->getProduct($product);
$tileDetails = $this->tileDetailsRepository->findOneByProduct($product);
if (!$item instanceof OrderItemInterface) {
$item = $this->orderItemRepository->find((int) $item->getId());
}
$response = $this->render('@SyliusShop/Layout/Modal/cart_item_attributes.html.twig', [
'product' => $product,
'item' => $item,
'tileDetails' => $tileDetails,
'isTile' => $this->hasTileTaxon($product),
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderImages(array|string|int|ProductInterface $product, Request $request): Response
{
$product = $this->getProduct($product);
$response = $this->render('@SyliusShop/Product/Show/newImages.html.twig', [
'product' => $product,
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderImagesEsi(string $name, bool $isFinishingPiece, string $images = '', Request $request): Response
{
$product = [
'name' => $name,
'isFinishingPiece' => $isFinishingPiece,
'images' => array_filter(json_decode($images, true)),
];
$response = $this->render('@SyliusShop/Product/Show/newImages.html.twig', [
'product' => $product,
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderImagesLower(string|int|ProductInterface $product, Request $request): Response
{
$product = $this->getProduct($product);
$response = $this->render('@SyliusShop/Product/Show/newImagesLow.html.twig', [
'product' => $product,
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderTopAttributes(string|int|ProductInterface $product, Request $request): Response
{
$product = $this->getProduct($product);
$attributes = $product->getAttributesByLocale($this->localeContext->getLocaleCode(), 'en_US');
$attributeList = [];
foreach ($attributes as $attribute) {
$code = $attribute->getCode();
$attributeList[$code] = $attribute;
}
$orderedList = [];
$top = ['SLIP_CLASS', 'EDGE', 'SHADE_VARIATION', 'THICKNESS']; // todo: move to CMS
foreach ($top as $item) {
$orderedList[$item] = $attributeList[$item] ?? null;
}
$response = $this->render('@SyliusShop/Product/Show/_topAttributes.html.twig', [
'product' => $product,
'attributes' => $orderedList,
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderPricing(string|int|ProductInterface $product, Request $request): Response
{
$product = $this->getProduct($product);
$tileDetails = $this->tileDetailsRepository->findOneByProduct($product);
$response = $this->render('@SyliusShop/Product/Show/newPrice.html.twig', [
'product' => $product,
'isTile' => $this->hasTileTaxon($product),
'tileDetails' => $tileDetails,
'variations' => $product->getEnabledVariants(),
'options' => $product->getOptions(),
]);
return $response;
$response->setPublic();
$response->setMaxAge(0);
return $response;
}
public function renderCartItemPricing(string|int|ProductInterface $product, OrderItemInterface $item): Response
{
$product = $this->getProduct($product);
$tileDetails = $product->getTileDetails()->first();
if (empty($tileDetails)) {
$tileDetails = null;
}
$response = $this->render('@SyliusShop/Layout/Modal/cart_item_pricing.html.twig', [
'product' => $product,
'isTile' => $this->hasTileTaxon($product),
'tileDetails' => $tileDetails,
'item' => $item,
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderNewAttributes(string|int|ProductInterface $product, Request $request): Response
{
$product = $this->getProduct($product);
$response = $this->render('@SyliusShop/Product/Show/newAttributes.html.twig', [
'product' => $product,
'attributes' => $product->getAttributesByLocale($this->localeContext->getLocaleCode(), 'en_US'),
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
public function renderProductsFromCollection(string|int|ProductInterface $product): Response
{
$product = $this->getProduct($product);
$taxons = $product->getTaxons();
/** @var Product[] $products */
$products = [];
$taxon = null;
foreach ($taxons as $taxon) {
if ($taxon->getLevel() === 2 && $taxon->getRoot()->getCode() === 'collection') {
$products = $this->productRepository
->createListQueryBuilder($this->localeContext->getLocaleCode(), $taxon->getId())
->andWhere('o.id != :prd_id')
->andWhere('o.enabled = :enabled')
->setParameter('prd_id', $product->getId())
->setParameter('enabled', true)
->setMaxResults(12)
->orderBy('RAND()', 'ASC')
->getQuery()
->getResult()
;
break;
}
}
$finishingPiecesTaxon = $this->taxonRepository->findOneBy(['code' => 'FINISHING_PIECES']);
foreach ($products as $key => $item) {
if ($finishingPiecesTaxon !== null && $product->hasTaxon($finishingPiecesTaxon)) {
unset($products[$key]);
}
}
$response = $this->render('@SyliusShop/Product/showProductsFromCollection.html.twig', [
'products' => $products,
'taxon' => $taxon,
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
#[Route('api/v3/prices')]
public function getPrices(Request $request): JsonResponse
{
$result = [];
$data = $request->toArray();
$channel = $this->channelContext->getChannel();
if (isset($data['product'])) {
foreach ((array) $data['product'] as $product) {
$product = $this->getProduct($product);
$variant = $product->getVariants()->first();
if ($variant === false) {
continue;
}
$price = $this->priceExtension->calculatePriceWithTax($variant, ['channel' => $channel]);
$convertedPrice = $this->convertMoneyHelper->convertAmount($price, $channel->getBaseCurrency()?->getCode(), $this->currencyContext->getCurrencyCode());
$result[] = [
'product' => $product->getId(),
'price' => $convertedPrice,
'lowestMarketingPrice' => '',
'onSale' => false,
'oldPrice' => '',
];
}
}
return $this->json($result);
}
public function renderAdminVariationCalculation(string|int|ProductInterface $product, Request $request): Response
{
$product = $this->getProduct($product);
$isCalculated = true;
$isTile = !$product->getTileDetails()->isEmpty();
if (!$isTile) {
$queued = $this->productVariantCalculationRepository->findOneBy(['product' => $product, 'status' => 'queued']);
$processing = $this->productVariantCalculationRepository->findOneBy(['product' => $product, 'status' => 'processing']);
$isCalculated = $queued === null && $processing === null;
}
$response = $this->render('@SyliusAdmin/Product/Show/variantionCalculation.html.twig', [
'product' => $product,
'isTile' => $isTile,
'isPricesCalculated' => $isCalculated,
'attributes' => $product->getAttributesByLocale($this->localeContext->getLocaleCode(), 'en_US'),
]);
return $response;
$response->setPublic();
$response->setMaxAge(604800);
return $response;
}
private function getProductCollection(ProductInterface $product): ?TaxonInterface
{
$taxons = $product->getTaxons();
foreach ($taxons as $taxon) {
if ($taxon->getLevel() === 2 && $this->isTaxonCollection($taxon)) {
return $taxon;
}
}
return null;
}
private function isTaxonCollection(TaxonInterface $taxon): bool
{
return $taxon->getRoot()?->getCode() === 'collection';
}
private function hasTileTaxon(ProductInterface $product): bool
{
// TODO: Implement hasTileTaxon
return false;
}
#[Route(path: '/admin/import-export/brands/products/export', name: 'app_admin_import_admin_products_export')]
public function export(LocaleProviderInterface $localeProvider): Response
{
$headers = ['PRODUCT_CODE', 'BRAND_CODE', 'PRICE', 'ORIGINAL_PRICE'];
//$products = $this->productRepository->findBy(['enabled' => true]);
return new StreamedResponse(
function () use ($headers) {
$fp = fopen('php://output', 'wb');
$writer = Writer::createFromStream($fp);
$sep = ';';
$writer->setDelimiter($sep);
$writer->insertAll([$headers]);
$this->outputProductData($writer);
},
200,
[
'content-type' => 'text/csv',
'content-disposition' => sprintf('attachment;filename="brands_products_%s.csv"', date('Ymd_his')),
]
);
}
private function outputProductData(Writer $writer): void
{
//$offset = 0;
//$limit = 5000;
$statement = $this->entityManager->getConnection()->prepare(
'SELECT p.code, b.code,
COALESCE(CASE
WHEN d.unit = "mq" THEN ROUND(MIN(cp.price / d.sqmPerBox) / 100, 2)
WHEN d.unit = "mt" THEN ROUND(MIN(cp.price / d.lmPerBox) / 100, 2)
WHEN d.unit = "pcs" THEN ROUND(MIN(cp.price / d.pcsPerBox) / 100, 2)
ELSE ROUND(MIN(cp.price) / 100, 2) END, 0) as `price`,
COALESCE(CASE
WHEN d.unit = "mq" THEN ROUND(MIN(cp.original_price / d.sqmPerBox) / 100, 2)
WHEN d.unit = "mt" THEN ROUND(MIN(cp.original_price / d.lmPerBox) / 100, 2)
WHEN d.unit = "pcs" THEN ROUND(MIN(cp.original_price / d.pcsPerBox) / 100, 2)
ELSE ROUND(MIN(cp.original_price) / 100, 2) END, 0) as `original_price`
FROM sylius_product p
LEFT JOIN loevgaard_brand b ON b.id = p.brand_id
LEFT JOIN sylius_product_tile_details d on d.product_id = p.id
LEFT JOIN sylius_product_variant v ON v.product_id = p.id
LEFT JOIN sylius_channel_pricing cp ON cp.product_variant_id = v.id
WHERE p.enabled = true
GROUP BY p.id
');
$result = $statement->executeQuery();
while (($line = $result->fetchNumeric()) !== false) {
$writer->insertOne($line);
}
//
//do {
// $results = $this->productRepository->findBy(['enabled' => true], limit: $limit, offset: $offset);
// /** @var Product $product */
// foreach ($results as $product) {
// $line = [
// $product->getCode(),
// $product->getBrand()?->getCode(),
// ];
// $writer->insertOne($line);
// }
// $offset += $limit;
//} while (!empty($results));
}
#[Route(path: '/export/products-without-images/csv', methods: ['GET'], name: 'products_without_images')]
public function getCsvListWithoutImages(Request $request): Response
{
$content[] = 'CODE';
//$andWhere = '';
//$taxons = [];
$exclude = array_filter(explode(',', $request->query->get('exclude', '')));
//$codes = [];
//foreach ($exclude as $key => $categoryCode) {
// $taxons['taxon'.$key] = $this->taxonRepository->findOneBy(['code' => $categoryCode]);
// $andWhere .= ' and pt.taxon != :taxon'.$key;
//}
//
//if (!empty($only = $request->query->get('only', ''))) {
// $taxons['only'] = $this->taxonRepository->findOneBy(['code' => $only]);
// $andWhere .= ' and pt.taxon = :only';
//}
set_time_limit(99999);
//$dql = "SELECT distinct p.code, t.code as cat from App\Entity\Product\Product p join p.productTaxons pt join pt.taxon t where p.images is empty and t.code not in(:codes) ";
//$dql = "SELECT p from App\Entity\Product\Product p join p.productTaxons pt where p.images is empty";
$dql = "SELECT p.code from App\Entity\Product\Product p where p.images is empty and p.id not in (select distinct p2.id from App\Entity\Product\Product p2 join p2.productTaxons pt join pt.taxon t where p2.images is empty and t.code in(:codes))";
if ($request->query->get('enabled') !== null) {
$dql .= ' and p.enabled = :enabled';
}
$query = $this->entityManager->createQuery($dql);
//$query->setParameters($taxons);
$query->setParameter('codes', $exclude);
if ($request->query->get('enabled') !== null) {
$query->setParameter('enabled', $request->query->get('enabled'));
}
$rows = $query->getArrayResult();
/** @var Product $product */
foreach ($rows as $product) {
//if (!empty($exclude)) {
// foreach ($taxons as $taxon) {
// if ($product->getTaxons()->contains($taxon)) {
// continue 2;
// }
// }
//}
//$values = array_values($row);
$content[] = sprintf('"%s"', $product['code']/*, $values[1]*/);
}
return new Response(implode("\r\n", $content), 200, ['Content-Type' => 'text/csv']);
}
private function getProduct(string|int|ProductInterface $product): ProductInterface
{
if (is_scalar($product)) {
$product = $this->productRepository->find((int) $product);
}
return $product;
}
}