<?php
namespace App\Controller;
use App\Model\DataObject\Decor;
use App\Model\DataObject\VirtualProduct;
use App\Templating\Helper\ConfigHelper;
use App\Tools\IndexHelper;
use App\Tools\LocaleHelper;
use App\Tools\SelectOptionsHelper;
use App\Twig\LinkGenerator;
use Elements\Bundle\SeoHelperBundle\Templating\Helper\Robots;
use Elements\Bundle\TrackingBundle\Tracking\TrackingManager;
use Knp\Component\Pager\PaginatorInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
use Pimcore\Bundle\EcommerceFrameworkBundle\FilterService\ListHelper;
use Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\ProductList\ProductListInterface;
use Pimcore\Log\ApplicationLogger;
use Pimcore\Model\DataObject;
use Pimcore\Model\DataObject\Application;
use Pimcore\Model\DataObject\ApplicationArea;
use Pimcore\Model\DataObject\Product;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;
class ProductController extends AbstractController
{
protected const STEP_TO_FILTER = [
'selection1' => 'applicationAreas',
'selection2' => 'allApplications',
'selection3' => 'decorGroup',
'selection4' => 'allCharacteristics',
];
protected const STEP_TO_TRACKING_PARAM = [
'selection1' => 'location',
'selection2' => 'application',
'selection3' => 'decor_group',
'selection4' => 'features',
];
protected DataObject\SiteConfig $siteConfig;
protected array $applicationAreaMapping = [];
public function __construct(ApplicationLogger $applicationLogger, ConfigHelper $configHelper)
{
$this->siteConfig = $configHelper->getSiteConfig();
$this->applicationAreaMapping = [
$this->siteConfig->getInteriorArea()?->getId() => 'interior',
$this->siteConfig->getExteriorArea()?->getId() => 'exterior',
$this->siteConfig->getUniveralArea()?->getId() => 'universal',
];
parent::__construct($applicationLogger);
}
public function overviewAction(Request $request, PaginatorInterface $paginator, Factory $factory, ListHelper $listHelper) {
$templateParams = [];
$params = array_merge($request->query->all(), $request->request->all());
$filterDefinition = $this->getDocumentEditable('relation', 'filterDefinition')->getElement();
$filterService = $factory->getFilterService();
if (empty($filterDefinition)) {
$filterDefinition = \Pimcore\Config::getWebsiteConfig()->fallbackFilterdefinition;
}
$templateParams['filterDefinition'] = $filterDefinition;
//product listing for filter
$productListing = $factory->getIndexService()->getProductListForCurrentTenant();
$productListing->setVariantMode(ProductListInterface::VARIANT_MODE_HIDE);
$productListing->addCondition(IndexHelper::INDEX_O_CLASS_ID . ' = "VirtualProduct"');
$listHelper->setupProductList($filterDefinition, $productListing, $params, $filterService, true);
$templateParams['productListing'] = $productListing;
// product listing
$products = $factory->getIndexService()->getProductListForCurrentTenant();
$products->setVariantMode(ProductListInterface::VARIANT_MODE_HIDE);
$products->addCondition(IndexHelper::INDEX_O_CLASS_ID . ' = "VirtualProduct"');
// apply filters
$listHelper->setupProductList($filterDefinition, $products, $params, $filterService, true);
$templateParams['filterService'] = $filterService;
$templateParams['currentFilter'] = $params['currentFilter'];
// paginate
$paging = $paginator->paginate($products, $request->get('page', 1), $filterDefinition->getPageLimit());
$templateParams['products'] = $paging;
$trackingManager = $factory->getTrackingManager();
foreach ($paging as $product) {
$trackingManager->trackProductImpression($product);
}
if ($request->isXmlHttpRequest()) {
return $this->json([
'success' => true,
'html' => $this->renderView('product/includes/product-grid-container.html.twig', $templateParams)
]);
}
return $this->renderTemplate('product/overview.html.twig', $templateParams);
}
public function productFinderAction(Request $request, SessionInterface $session): Response
{
$step = (int)$request->get('step', 1);
while ((($request->get('back', false) && $step > 1) || $step < 4) && $this->document->getEditable('options' . $step)?->isEmpty()) {
if ($request->get('back', false)) {
$step--;
} else {
$step++;
}
}
$selection = $session->get('selection', [
'selection1' => [],
'selection2' => [],
'selection3' => [],
'selection4' => [],
]);
$options = $this->document->getEditable('options' . $step)?->getValue();
$returnArray = [
'step' => $step,
'ajax' => $request->get('ajax', false),
'back' => $request->get('back', false),
'selection' => $selection,
'options' => $options,
];
if ($request->isMethod('POST')) {
$selection['selection' . $step] = $this->getSelection($request->get('selection' . $step, []));
$session->set('selection', $selection);
if ($request->get('step') < 4) {
$step++;
while ((($request->get('back', false) && $step > 1) || $step < 4) && $this->document->getEditable('options' . $step)?->isEmpty()) {
if ($request->get('back', false)) {
$step--;
} else {
$step++;
}
}
$returnArray['step'] = $step;
$returnArray['selection'] = $selection;
$options = $this->document->getEditable('options' . $step)?->getValue();
if ($step === 2 && !empty(array_filter($selection['selection1']))) {
$selectedApplicationAreas = array_intersect_key($this->applicationAreaMapping, $selection['selection1']);
$options = array_filter($options, static function (Application $o) use ($selectedApplicationAreas) {
return in_array($o->getApplicationArea(), $selectedApplicationAreas, true);
});
}
//NUR beim letzten Schritt das "responseTrackingData" mit den Daten aus dem "$gtm"-Objekt zur json response dazu geben
$returnArray['options'] = $options;
return $this->json([
'success' => true,
'html' => $this->renderTemplate('product/includes/productfinder-form.html.twig', $returnArray)->getContent(),
], 200, ['X-Robots-Tag' => 'noindex']);
}
if ($this->siteConfig && $this->siteConfig->getProductOverview()) {
$params = [];
$trackingParams = ["event" => "product_finder_finished"];
foreach ($selection as $step => $values) {
if (array_key_exists($step, self::STEP_TO_TRACKING_PARAM)) {
$trackingParams[self::STEP_TO_TRACKING_PARAM[$step]] = implode(', ', $values);
}
if (array_key_exists($step, self::STEP_TO_FILTER)) {
foreach (array_keys($values) as $id) {
$params[self::STEP_TO_FILTER[$step]][] = $id;
}
}
}
$this->addFlash('tracking', $trackingParams);
$query = http_build_query($params);
$query = preg_replace('/%5B\d+%5D/simU', '%5B%5D', $query);
$session->clear();
return $this->redirect($this->siteConfig->getProductOverview()->getFullPath() . '?' . $query . '#grid');
}
}
if ($step === 2 && !empty(array_filter($selection['selection1']))) {
$selectedApplicationAreas = array_intersect_key($this->applicationAreaMapping, $selection['selection1']);
$options = array_filter($options, static function (Application $o) use ($selectedApplicationAreas) {
return in_array($o->getApplicationArea(), $selectedApplicationAreas, true);
});
$returnArray['options'] = $options;
}
if ($request->get('ajax')) {
return $this->json([
'success' => true,
'html' => $this->renderTemplate('product/includes/productfinder-form.html.twig', $returnArray)->getContent()
], 200, ['X-Robots-Tag' => 'noindex']);
}
return $this->renderTemplate('product/productfinder.html.twig', $returnArray);
}
/**
* @Route ("/product-finder-banner", name="product_finder_banner")
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function productFinderBanner(Request $request, SessionInterface $session)
{
$selection = $session->get('selection', [
'selection1' => [],
'selection2' => [],
'selection3' => [],
'selection4' => [],
]);
$selection['selection1'] = $this->getSelection($request->get('selection1', []));
$session->set('selection', $selection);
$page = $this->siteConfig?->getProductFinder(LocaleHelper::LANG_DE);
return $this->redirect($page?->getFullPath().'?step=2');
}
public function materialGroupOverviewAction(Request $request) {
return $this->renderTemplate('product/material-group-overview.html.twig');
}
public function detailAction(Request $request, Factory $factory, LinkGenerator $linkGenerator): Response
{
$product = $this->getValidDetailObject($request, VirtualProduct::class);
if ($request->getPathInfo() !== $linkGenerator->generate($product)) {
return $this->redirect($linkGenerator->generate($product));
}
$trackingManager = $factory->getTrackingManager();
$trackingManager->trackProductView($product);
return $this->renderTemplate('product/detail.html.twig', [
'product' => $product,
'similarProducts' => $this->getSimilarProducts($product, $factory)
]);
}
/**
* @Route("/{_locale}/product-gallery-images/{product}/{page}", name="_product_gallery_images")
*/
public function getGalleryImages(VirtualProduct $product, $page) {
$from = 3 * ($page - 1);
$to = $from + 3;
return $this->json([
'success' => true,
'html' => $this->renderView('product/includes/image-gallery.html.twig', ['images' => $product->getGalleryItems($from, $to), 'product' => $product, 'containerId' => 'product-gallery-' . $product->getId(), 'page' => $page])
]);
}
protected function getSelection(array $ids): array
{
$selection = [];
foreach ($ids as $id) {
$object = DataObject::getById($id);
if ($object && method_exists($object, 'getTitle')) {
$selection[$id] = $object->getTitle();
}
}
return $selection;
}
protected function getSimilarProducts(
VirtualProduct|DataObject\AbstractObject|DataObject\Concrete|null $product,
Factory $factory
) {
if ($product->getDecor() instanceof Decor) {
$products = $factory->getIndexService()->getProductListForCurrentTenant();
$products->setVariantMode(ProductListInterface::VARIANT_MODE_HIDE);
$products->addCondition(IndexHelper::INDEX_O_CLASS_ID . ' = "VirtualProduct"');
$products->addCondition('o_id != ' . $product->getId() . ' AND o_id IN (SELECT src FROM ' . IndexHelper::TABLE_PRODUCT_INDEX_RELATIONS . ' WHERE fieldname = "decor" AND dest = "' . $product->getDecor()->getId() . '" GROUP BY src)');
$products->setOrderKey('RAND()');
$products->setLimit(3);
return $products;
}
return [];
}
}