custom/plugins/SwagHoggi/src/Storefront/Controller/HoggiConfiguratorController.php line 610

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace SwagHoggi\Storefront\Controller;
  4. use RuntimeException;
  5. use Shopware\Core\Checkout\Cart\Cart;
  6. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  7. use Shopware\Core\Checkout\Cart\LineItemFactoryRegistry;
  8. use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
  9. use Shopware\Core\Checkout\Customer\CustomerEntity;
  10. use Shopware\Core\Content\Media\MediaEntity;
  11. use Shopware\Core\Content\Product\ProductEntity;
  12. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  16. use Shopware\Core\Framework\Uuid\Uuid;
  17. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  18. use Shopware\Storefront\Controller\StorefrontController;
  19. use SwagHoggi\Core\Content\Hoggi\Configurator\ConfiguratorEntity;
  20. use SwagHoggi\Core\Content\Hoggi\Configurator\ConfiguratorProductCustomFieldEntity;
  21. use SwagHoggi\Core\Content\Hoggi\Configurator\SavedConfigurationEntity;
  22. use SwagHoggi\Service\Checkout\Cart\HoggiConfiguratorCartItemHandler;
  23. use SwagHoggi\Storefront\Page\HoggiConfigurator\HoggiConfiguratorPageLoader;
  24. use SwagHoggi\Storefront\Page\SavedConfigurationListing\SavedConfigurationListingPageLoader;
  25. use Symfony\Component\HttpFoundation\JsonResponse;
  26. use Symfony\Component\HttpFoundation\Request;
  27. use Symfony\Component\HttpFoundation\Response;
  28. use Symfony\Component\Routing\Annotation\Route;
  29. /**
  30.  * @Route(defaults={"_routeScope"={"storefront"}})
  31.  */
  32. class HoggiConfiguratorController extends StorefrontController
  33. {
  34.     private EntityRepository $configuratorRepository;
  35.     private EntityRepository $productRepository;
  36.     private EntityRepository $savedConfigurationRepository;
  37.     private LineItemFactoryRegistry $factory;
  38.     private CartService $cartService;
  39.     private HoggiConfiguratorPageLoader $configuratorPageLoader;
  40.     private SavedConfigurationListingPageLoader $savedConfiguratorListingPageLoader;
  41.     private EntityRepository $configuratorProductCustomFieldsRepository;
  42.     /**
  43.      * @param EntityRepository $configuratorRepository
  44.      * @param EntityRepository $productRepository
  45.      * @param LineItemFactoryRegistry $factory
  46.      * @param CartService $cartService
  47.      * @param HoggiConfiguratorPageLoader $configuratorPageLoader
  48.      */
  49.     public function __construct(
  50.         EntityRepository $configuratorRepository,
  51.         EntityRepository $productRepository,
  52.         EntityRepository $savedConfigurationRepository,
  53.         LineItemFactoryRegistry $factory,
  54.         CartService $cartService,
  55.         HoggiConfiguratorPageLoader $configuratorPageLoader,
  56.         SavedConfigurationListingPageLoader $savedConfiguratorListingPageLoader,
  57.         EntityRepository $configuratorProductCustomFieldsRepository
  58.     ) {
  59.         $this->configuratorRepository $configuratorRepository;
  60.         $this->productRepository $productRepository;
  61.         $this->savedConfigurationRepository $savedConfigurationRepository;
  62.         $this->factory $factory;
  63.         $this->cartService $cartService;
  64.         $this->configuratorPageLoader $configuratorPageLoader;
  65.         $this->savedConfiguratorListingPageLoader $savedConfiguratorListingPageLoader;
  66.         $this->configuratorProductCustomFieldsRepository $configuratorProductCustomFieldsRepository;
  67.     }
  68.     /**
  69.      * @param Request $request
  70.      * @param Cart $cart Current cart
  71.      * @param SalesChannelContext $context Current context
  72.      * @return Response
  73.      * @Route("/hoggi-configurator/addcart", name="frontend.hoggi-configurator.add-cart", methods={"POST"}, defaults={"csrf_protected"=false, "XmlHttpRequest"=true})
  74.      */
  75.     public function addCart(Request $requestCart $cartSalesChannelContext $context): Response
  76.     {
  77.         $productsIds $request->request->get("products") ?: [];
  78.         $comment $request->request->get("comment") ?? "";
  79.         $configurator $request->request->get("configurator") ?: [];
  80.         $configuratorId $request->request->get("configuratorId") ?: null;
  81.         $savedConfigurationName $request->request->get("savedConfigurationName") ?: null;
  82.         $savedConfigurationId $request->request->get("savedConfigurationId") ?: null;
  83.         // clear cart
  84.         /*
  85.         foreach ($cart->getLineItems()->filterFlatByType(HoggiConfiguratorCartItemHandler::TYPE) as $lineItem) {
  86.             $this->factory->update($cart, [
  87.                 "id" => $lineItem->getId(),
  88.                 "removable" => true
  89.             ], $context);
  90.             $this->cartService->remove($cart, $lineItem->getId(), $context);
  91.         }
  92.         */
  93.         /** @var array $products */
  94.         $products array_map(function ($productId) use ($context$configuratorId) {
  95.             /** @var ProductEntity $product */
  96.             $product $this->productRepository->search(new Criteria([$productId]), $context->getContext())->first();
  97.             if ($product === null) {
  98.                 throw new RuntimeException("Product id '$productId' not found!");
  99.             }
  100.             $data $product->getCustomFields();
  101.             if (empty($data) || (is_array($data) && count($data) <= 0)) {
  102.                 $criteria = new Criteria();
  103.                 $criteria->addFilter(new AndFilter([
  104.                         new EqualsFilter("configurator"$configuratorId),
  105.                         new EqualsFilter("productNumber"$product->getProductNumber())
  106.                     ]
  107.                 ));
  108.                 /** @var ConfiguratorProductCustomFieldEntity $configuratorProductCustomFields */
  109.                 $configuratorProductCustomFields $this->configuratorProductCustomFieldsRepository->search($criteria$context->getContext())->first();
  110.                 if ($configuratorProductCustomFields) {
  111.                     $data $configuratorProductCustomFields->getCustomFields();
  112.                     $product->setCustomFields($data);
  113.                 }
  114.             }
  115.             return [
  116.                 "productId" => $productId,
  117.                 "productNumber" => $product->getProductNumber(),
  118.                 "outputOrder" => (int)($product->getCustomFields()['output_order'] ?? PHP_INT_MAX),
  119.                 "smallHint" => $product->getCustomFields()['small_hint'] ?? null
  120.             ];
  121.         }, $productsIds);
  122.         usort($products, function (array $a, array $b) {
  123.             // sort by output_order
  124.             if ($a["outputOrder"] < $b["outputOrder"]) {
  125.                 return -1;
  126.             } elseif ($a["outputOrder"] > $b["outputOrder"]) {
  127.                 return 1;
  128.             }
  129.             // sort by product number/sku
  130.             $skuCompare strcmp($a["productNumber"], $b["productNumber"]);
  131.             if ($skuCompare !== 0) {
  132.                 return $skuCompare;
  133.             }
  134.             // more maybe..?
  135.             return 0;
  136.         });
  137.         $cart->setCustomerComment($comment);
  138.         $this->cartService->recalculate($cart$context);
  139.         $configuratorItem $this->factory->create([
  140.             "type" => HoggiConfiguratorCartItemHandler::TYPE,
  141.             "savedConfigurationName" => $savedConfigurationName,
  142.             "savedConfigurationId" => $savedConfigurationId,
  143.             "referenceId" => $configuratorId,
  144.             "configurator" => $configurator,
  145.             "removable" => true
  146.         ], $context);
  147.         // dump($configuratorItem); die;
  148.         // add products
  149.         foreach ($products as ["productId" => $productId"outputOrder" => $outputOrder"smallHint" => $smallHint]) {
  150.             // Create product line item
  151.             $lineItem $this->factory->create([
  152.                 'type' => LineItem::PRODUCT_LINE_ITEM_TYPE// Results in 'product'
  153.                 'referencedId' => $productId// this is not a valid UUID, change this to your actual ID!
  154.                 'quantity' => 1,
  155.                 'payload' => ['type' => 'hoggi-configurator''output_order' => $outputOrder'small_hint' => $smallHint],
  156.             ], $context);
  157.             // $this->cartService->add($cart, $lineItem, $context);
  158.             $configuratorItem->getChildren()->add($lineItem);
  159.         }
  160.         // Add the configurator itself to cart
  161.         $this->cartService->add($cart$configuratorItem$context);
  162.         // dump($cart->getLineItems()); die;
  163.         return new JsonResponse(true);
  164.     }
  165.     /**
  166.      * @Route("/hoggi-configurator/{id}/structure", name="frontend.hoggi-configurator.structure", methods={"GET"}, defaults={"XmlHttpRequest"=true})
  167.      */
  168.     public function structure(string $idSalesChannelContext $context): Response
  169.     {
  170.         $criteria = new Criteria();
  171.         $criteria->setLimit(1);
  172.         $criteria->setIds([$id]);
  173.         $criteria->addAssociation("products");
  174.         $criteria->addAssociation("products.cover.media");
  175.         /** @var ConfiguratorEntity $configurator */
  176.         $configurator $this->configuratorRepository->search($criteria$context->getContext())->first();
  177.         $layout $configurator->getLayout();
  178.         $tabData = [];
  179.         foreach ($layout["tabs"] as $layoutTab) {
  180.             $type $layoutTab["type"] ?? "product";
  181.             switch ($type) {
  182.                 case "product":
  183.                     $newTabData $this->structureProductTab($configurator$layoutTab$context);
  184.                     $tabData array_merge($tabData$newTabData);
  185.                     break;
  186.                 case "matrix_config":
  187.                     $newTabData $this->structureMatrixConfigTab($configurator$layoutTab$context);
  188.                     $tabData array_merge($tabData$newTabData);
  189.                     break;
  190.             }
  191.         }
  192.         $tabs array_values($tabData);
  193.         foreach ($tabs as $tabIndex => $tab) {
  194.             $optionGroupsData = [];
  195.             foreach ($tab["sections"] as $section)
  196.                 foreach ($section["products"] as $product) {{
  197.                     $optionGroup $product["optionGroup"];
  198.                     if ($optionGroup === null) {
  199.                         continue;
  200.                     }
  201.                     $optionGroupId $optionGroup["id"];
  202.                     if (!array_key_exists($optionGroupId$optionGroupsData)) {
  203.                         $optionGroupsData[$optionGroupId] = $optionGroup + [
  204.                             "sectionCount" => 1
  205.                         ];
  206.                     } else {
  207.                         $optionGroupsData[$optionGroupId]["sectionCount"] += 1;
  208.                     }
  209.                     // only take first option group of section
  210.                     break;
  211.                 }
  212.             }
  213.             $tabs[$tabIndex]["optionGroups"] = array_values($optionGroupsData);
  214.         }
  215.         $data = [
  216.             "id" => $id,
  217.             "title" => $layout["title"] ?? null,
  218.             "theme" => $layout["theme"] ?? null,
  219.             "config" => $layout["config"] ?? [],
  220.             "tabs" => $tabs
  221.         ];
  222.         return new JsonResponse($data);
  223.     }
  224.     private function structureProductTab(ConfiguratorEntity $configurator, array $layoutTabSalesChannelContext $context): array
  225.     {
  226.         $tabData = [];
  227.         $sectionData $this->structureSections($layoutTab["sections"] ?? []);
  228.         $tabId $layoutTab["id"];
  229.         $tabData[$tabId] = [
  230.             "id" => $tabId,
  231.             "type" => "product",
  232.             "name" => $layoutTab["title"] ?? $tabId,
  233.             "sections" => $sectionData
  234.         ];
  235.         $products $configurator->getProducts();
  236.         /** @var ProductEntity $product */
  237.         foreach ($products as $product) {
  238.             $data $product->getCustomFields();
  239.             if (empty($data) || (is_array($data) && count($data) <= 0)) {
  240.                 $criteria = new Criteria();
  241.                 $criteria->addFilter(new AndFilter([
  242.                         new EqualsFilter("configurator"$configurator->getId()),
  243.                         new EqualsFilter("productNumber"$product->getProductNumber())
  244.                     ]
  245.                 ));
  246.                 /** @var ConfiguratorProductCustomFieldEntity $configuratorProductCustomFields */
  247.                 $configuratorProductCustomFields $this->configuratorProductCustomFieldsRepository->search($criteria$context->getContext())->first();
  248.                 if ($configuratorProductCustomFields) {
  249.                     $data $configuratorProductCustomFields->getCustomFields();
  250.                     $product->setCustomFields($data);
  251.                 }
  252.             }
  253.             $tabId $data["configurator_tab"] ?? null;
  254.             $sectionId $data["configurator_tab_section"] ?? $tabId;
  255.             if (!$tabId) {
  256.                 continue;
  257.             }
  258.             if (!isset($tabData[$tabId]["sections"][$sectionId])) {
  259.                 continue;
  260.             }
  261.             $currentSection = &$tabData[$tabId]["sections"][$sectionId];
  262.             $productData $this->buildProductData($product$currentSection["canHide"], $context);
  263.             $currentSection["products"][] = $productData;
  264.         }
  265.         foreach ($tabData as &$tab) {
  266.             $tab["sections"] = array_values($tab["sections"]);
  267.             foreach ($tab["sections"] as &$section) {
  268.                 $this->orderProducts($section["products"]);
  269.             }
  270.         }
  271.         return $tabData;
  272.     }
  273.     private function structureSections(array $layoutSections): array {
  274.         $sectionData = [];
  275.         foreach ($layoutSections as $layoutSection) {
  276.             $sectionId $layoutSection["id"];
  277.             $sectionData[$sectionId] = [
  278.                 "id" => $sectionId,
  279.                 "name" => $layoutSection["title"] ?? $sectionId,
  280.                 "canHide" => $layoutSection["canHide"] ?? true,
  281.                 "hideIncompatible" => $layoutSection["hideIncompatible"] ?? false,
  282.                 "hideInSummary" => $layoutSection["hideInSummary"] ?? false,
  283.                 "unselectAutomatic" => (bool)($layoutSection["unselectAutomatic"] ?? false),
  284.                 "cover" => $layoutSection["cover"] ?? null,
  285.                 "coverDirection" => $layoutSection["coverDirection"] ?? "left",
  286.                 "hint" => $layoutSection["hint"] ?? null,
  287.                 "products" => [],
  288.                 "type" => $layoutSection["type"] ?? "product",
  289.                 "options" => $layoutSection["options"] ?? null,
  290.                 "contents" => $layoutSection["contents"] ?? [],
  291.                 "contentVerticalAlignment" => $layoutSection["contentVerticalAlignment"] ?? "top"
  292.             ];
  293.         }
  294.         return $sectionData;
  295.     }
  296.     private function orderProducts(array &$products) {
  297.         usort($products, function (array $productA, array $productB) {
  298.             return strcasecmp($productA["productNumber"], $productB["productNumber"]);
  299.         });
  300.     }
  301.     private function restExplode(string|array|null $valuebool $fixNumber false): array
  302.     {
  303.         if (is_array($value)) {
  304.             return $value;
  305.         }
  306.         if (empty($value)) {
  307.             return [];
  308.         }
  309.         if ($fixNumber && is_string($value) && preg_match("/^\d+\.\d+$/"trim($value)) === 1) {
  310.             // Assume tha excel changed a 2 element list into a number and try to fix it
  311.             $value str_replace("."","$value);
  312.         }
  313.         return array_filter(array_map("trim"explode(","$value)));
  314.     }
  315.     private function parsePreCondition(?string $preCondition): ?string {
  316.         if (empty($preCondition)) {
  317.             return null;
  318.         }
  319.         return preg_replace([
  320.             '/\s*\|\|\s*/',
  321.             '/\s*&&\s*/'
  322.         ], [
  323.             ' OR ',
  324.             ' AND '
  325.         ], $preCondition);
  326.     }
  327.     private function structureMatrixConfigTab(ConfiguratorEntity $configurator, array $layoutTabSalesChannelContext $context): array {
  328.         $tabId $layoutTab["id"];
  329.         $sectionData = isset($layoutTab["sections"]) ? $this->structureSections($layoutTab["sections"]) : [];
  330.         $products $configurator->getProducts();
  331.         $productData = [];
  332.         /** @var ProductEntity $product */
  333.         foreach ($products as $product) {
  334.             $data $product->getCustomFields();
  335.             if (empty($data) || (is_array($data) && count($data) <= 0)) {
  336.                 $criteria = new Criteria();
  337.                 $criteria->addFilter(new AndFilter([
  338.                         new EqualsFilter("configurator"$configurator->getId()),
  339.                         new EqualsFilter("productNumber"$product->getProductNumber())
  340.                     ]
  341.                 ));
  342.                 /** @var ConfiguratorProductCustomFieldEntity $configuratorProductCustomFields */
  343.                 $configuratorProductCustomFields $this->configuratorProductCustomFieldsRepository->search($criteria$context->getContext())->first();
  344.                 if ($configuratorProductCustomFields) {
  345.                     $data $configuratorProductCustomFields->getCustomFields();
  346.                     $product->setCustomFields($data);
  347.                 }
  348.             }
  349.             $productTabId $data["configurator_tab"] ?? null;
  350.             if ($productTabId === null || $productTabId !== $tabId) {
  351.                 continue; // doesnt belong to tab, skip
  352.             }
  353.             $productData[] = $this->buildProductData($productfalse$context);
  354.         }
  355.         $this->orderProducts($productData);
  356.         $layoutFields $layoutTab["config"]["fields"] ?? [];
  357.         $layoutMatrixType $layoutTab["config"]["type"] ?? null;
  358.         $layoutMatrix $layoutTab["config"]["matrix"] ?? [];
  359.         $fields = [];
  360.         foreach ($layoutFields as $layoutField) {
  361.             $field = [
  362.                 "id" => $layoutField["id"],
  363.                 "name" => $layoutField["title"],
  364.                 "type" => $layoutField["type"],
  365.                 "valueParent" => $layoutField["value_parent"] ?? null,
  366.                 "filterField" => $layoutField["filter_field"] ?? null,
  367.                 "requires" => $layoutField["requires"] ?? [],
  368.                 "valueFormat" => $layoutField["value_format"] ?? null,
  369.                 "values" => $layoutField["values"] ?? null,
  370.                 "productGroup" => $layoutField["product_group"] ?? null,
  371.                 "valueMeta" => $layoutField["value_meta"] ?? null,
  372.             ];
  373.             if ($field["id"] === "seat_width" && !empty($field["values"])) {
  374.                 $seatDepthEntries = [];
  375.                 foreach ($field["values"] as $value) {
  376.                     $productNumber $value["article_number"] ?? null;
  377.                     if ($productNumber === null) {
  378.                         continue;
  379.                     }
  380.                     $seatWidthProduct $this->findProductInDataByProductNumber($productData$productNumber);
  381.                     if ($seatWidthProduct === null) {
  382.                         continue;
  383.                     }
  384.                     $seatDepths $seatWidthProduct["seatDepths"] ?? null;
  385.                     if (empty($seatDepths)) {
  386.                         continue;
  387.                     }
  388.                     $seatDepthEntries[$productNumber] = $seatDepths;
  389.                 }
  390.                 $field["seatDepths"] = $seatDepthEntries;
  391.             }
  392.             if ($layoutField["type"] === "seat-depth") {
  393.                 $field["minValue"] = $layoutField["min_value"] ?? null;
  394.                 $field["maxValue"] = $layoutField["max_value"] ?? null;
  395.                 $field["extraCostProduct"] = $layoutField["extra_cost_article"] ?? null;
  396.             }
  397.             $fields[] = $field;
  398.         }
  399.         if ($sectionData) {
  400.             // remove string keys
  401.             $sectionData array_values($sectionData);
  402.         }
  403.         $tabData = [
  404.             "id" => $tabId,
  405.             "name" => $layoutTab["title"] ?? $tabId,
  406.             "type" => "matrix_config",
  407.             "config" => [
  408.                 "name" => $layoutTab["config"]["title"] ?? "",
  409.                 "fields" => $fields,
  410.                 "type" => $layoutMatrixType,
  411.                 "matrix" => $layoutMatrix,
  412.                 "cover" => $layoutTab["config"]["cover"] ?? null,
  413.                 "src" => $layoutTab["config"]["src"] ?? null,
  414.             ],
  415.             "sections" => $sectionData,
  416.             "products" => $productData
  417.         ];
  418.         return [$tabId => $tabData];
  419.     }
  420.     /**
  421.      * @param string|null $countPerGroupRaw
  422.      * @return array 0=>$countPerGroup, 1=>$optionGroup
  423.      */
  424.     private function parseCountPerGroupAndOptionGroup(?string $countPerGroupRaw): array
  425.     {
  426.         if (empty($countPerGroupRaw)) {
  427.             return [nullnull];
  428.         } else {
  429.             // Format is "id:name:countPerGroup" or "id:countPerGroup"
  430.             if (preg_match("~^([^:\n]+):(?:([^:\n]+):)?([^:\n]+)$~"$countPerGroupRaw$optionGroupMatch) === 1) {
  431.                 $optionGroup = [
  432.                     "id" => $optionGroupMatch[1],
  433.                     "name" => $optionGroupMatch[2] ?: $optionGroupMatch[1], // fallback to id
  434.                     "countPerGroup" => $optionGroupMatch[3],
  435.                 ];
  436.                 // "Turn off" section validation by using "0,N"
  437.                 return [$optionGroupMatch[3], $optionGroup];
  438.             } else {
  439.                 return [$countPerGroupRawnull];
  440.             }
  441.         }
  442.     }
  443.     private function getThumbnail(SalesChannelContext $context, ?MediaEntity $mediaint $targetHeightint $targetWidth): ?string
  444.     {
  445.         if ($media == null) {
  446.             return null;
  447.         }
  448.         $bestMatch null;
  449.         $minDistance PHP_INT_MAX;
  450.         foreach ($media->getThumbnails() as $thumbnail) {
  451.             $distance abs($thumbnail->getWidth() - $targetWidth) + abs($thumbnail->getHeight() - $targetHeight);
  452.             if ($distance $minDistance) {
  453.                 $minDistance $distance;
  454.                 $bestMatch $thumbnail;
  455.             }
  456.         }
  457.         if ($bestMatch) {
  458.             return $bestMatch->getUrl();
  459.         } else {
  460.             return $media->getUrl();
  461.         }
  462.     }
  463.     function buildProductData (ProductEntity $productbool $canHideSalesChannelContext $context): array
  464.     {
  465.         $data $product->getCustomFields();
  466.         $seatWidths array_map("intval"$this->restExplode($data['compatible_seatwidth'] ?? [], true));
  467.         $compatibleProducts $this->restExplode($data['compatible_product'] ?? null);
  468.         $incompatibleProducts $this->restExplode($data['incompatible_product'] ?? null);
  469.         $requiredProducts $this->restExplode($data['required_product'] ?? null);
  470.         $preConditions $this->parsePreCondition($data['pre_condition'] ?? null);
  471.         $wheelSizes array_map("intval"$this->restExplode($data['wheel_sizes'] ?? [], true));
  472.         $backHeights array_map("intval"$data['back_height'] ?? []);
  473.         $casterSizes array_map("intval"$data['caster_sizes'] ?? []);
  474.         $seatHeights array_map("intval"$data['seat_heights'] ?? []);
  475.         $seatDepths array_map("intval"$data['seat_depths'] ?? []);
  476.         $legLengths array_map("intval"$this->restExplode($data['leg_lengths'] ?? [], true));
  477.         $dropdownValues $data["dropdown_values"] ?? null;
  478.         list($countPerGroup$optionGroup) = $this->parseCountPerGroupAndOptionGroup($data['count_per_group'] ?? null);
  479.         return [
  480.             "id" => $product->getId(),
  481.             "name" => $product->getTranslation("name"),
  482.             // TODO Use thumbnails?
  483.             "cover" => $this->getThumbnail(
  484.                 $context$product->getCover()?->getMedia(),
  485.                 200,
  486.                 200
  487.             ),
  488.             'productNumber' => $product->getProductNumber(),
  489.             "canHide" => $canHide,
  490.             'hint' => null// TODO
  491.             "productGroup" => $data['product_group'] ?? null,
  492.             'productDescription' => $data['product_description'] ?? null,
  493.             'displayType' => $data['display_type'] ?? null,
  494.             'displayValue' => $data['display_value'] ?? null,
  495.             "seatWidths" => $seatWidths,
  496.             // Don't output compatible products, since they are not used
  497.             // 'compatibleProducts' => $compatibleProducts,
  498.             'compatibleProducts' => [],
  499.             'incompatibleProducts' => $incompatibleProducts,
  500.             'requiredProducts' => $requiredProducts,
  501.             'countPerGroup' => $countPerGroup,
  502.             'optionGroup' => $optionGroup,
  503.             'outputOrder' => $data['output_order'] ?? null,
  504.             'configuratorTab' => $data['configurator_tab'] ?? null,
  505.             'configuratorTabSection' => $data['configurator_tab_section'] ?? null,
  506.             'preCondition' => $preConditions,
  507.             'wheelSizes' => $wheelSizes,
  508.             'requiredSelections' => $data['required_selection'] ?? [],
  509.             'backHeights' => $backHeights,
  510.             'casterSizes' => $casterSizes,
  511.             'seatHeights'=> $seatHeights,
  512.             'seatDepths' => $seatDepths,
  513.             'legLengths' => $legLengths,
  514.             'dropdownValues' => $dropdownValues,
  515.             'description' => $product->getDescription(),
  516.             'smallHint' => $data["small_hint"] ?? null,
  517.         ];
  518.     }
  519.     /**
  520.      * @Route("/hoggi-configurator/{configuratorId}", name="frontend.hoggi-configurator.page", methods={"GET"}, defaults={"_loginRequired"=true})
  521.      */
  522.     public function hoggiConfiguratorPage(Request $requestCart $cartSalesChannelContext $context): Response
  523.     {
  524.         $page $this->configuratorPageLoader->load($request$cart$context);
  525.         return $this->renderStorefront('@SwagHoggi/storefront/page/hoggi-configurator/index.html.twig', [
  526.             'page' => $page
  527.         ]);
  528.     }
  529.     /**
  530.      * @Route("/hoggi-configurator/configurations/list", name="frontend.hoggi-configurator.saved-configuration.list", methods={"GET"}, defaults={"_loginRequired"=true})
  531.      */
  532.     public function savedConfigurationListingPage(Request $requestSalesChannelContext $context): Response
  533.     {
  534.         $page $this->savedConfiguratorListingPageLoader->load($request$context);
  535.         return $this->renderStorefront('@SwagHoggi/storefront/page/hoggi-configurator/saved-configuration-listing.html.twig', [
  536.             'page' => $page
  537.         ]);
  538.     }
  539.     /**
  540.      * @Route("/hoggi-configurator/configurations/remove/{configurationId}", name="frontend.hoggi-configurator.saved-configuration.remove", methods={"GET"}, defaults={"_loginRequired"=true})
  541.      */
  542.     public function savedConfigurationRemovePage(Request $requestCart $cartSalesChannelContext $context): Response
  543.     {
  544.         $customer $context->getCustomer();
  545.         $configurationId $request->attributes->get("configurationId");
  546.         $configurationCriteria = (new Criteria())->setLimit(1)
  547.             ->setIds([$configurationId])
  548.             ->addFilter(new EqualsFilter("customerId"$customer->getId()));
  549.         $configurationEntity $this->savedConfigurationRepository->search($configurationCriteria$context->getContext())->first();
  550.         if (!$configurationEntity) {
  551.             $this->addFlash("danger""Die Konfiguration existiert nicht");
  552.         } else {
  553.             $this->savedConfigurationRepository->delete([
  554.                 [
  555.                     "id" => $configurationId
  556.                 ]
  557.             ], $context->getContext());
  558.             $this->addFlash("success""Die Konfiguration wurde gelöscht");
  559.         }
  560.         return $this->redirectToRoute('frontend.hoggi-configurator.saved-configuration.list');
  561.     }
  562.     private function findProductInDataByProductNumber(array $productDatastring $productNumber): ?array {
  563.         foreach ($productData as $product) {
  564.             if ($product["productNumber"] === $productNumber) {
  565.                 return $product;
  566.             }
  567.         }
  568.         return null;
  569.     }
  570.     /**
  571.      * @param Request $request
  572.      * @param SalesChannelContext $context Current context
  573.      * @return Response
  574.      * @Route("/hoggi-configurator/save-configuration", name="frontend.hoggi-configurator.save-configuration", methods={"POST"}, defaults={"csrf_protected"=false, "XmlHttpRequest"=true})
  575.      */
  576.     public function saveConfiguration(Request $requestSalesChannelContext $context): Response {
  577.         /** @var string $name */
  578.         $name $request->request->get("name");
  579.         /** @var string|null $savedConfigurationId */
  580.         $savedConfigurationId $request->request->get("savedConfigurationId");
  581.         /** @var array $configuration */
  582.         $configuration $request->request->get("configurator") ?: [];
  583.         /** @var string $configuratorId */
  584.         $configuratorId $request->request->get("configuratorId");
  585.         /** @var CustomerEntity|null $customer */
  586.         $customer $context->getCustomer();
  587.         if (empty($name)) {
  588.             $name "Unbekannt";
  589.         }
  590.         if (empty($configuration)) {
  591.             throw new RuntimeException("No configuration data!");
  592.         }
  593.         if (empty($configuratorId)) {
  594.             throw new RuntimeException("No configurator id!");
  595.         }
  596.         if ($customer == null) {
  597.             throw new RuntimeException("Not logged in!");
  598.         }
  599.         $configuratorCriteria = (new Criteria([$configuratorId]))->setLimit(1);
  600.         /** @var ConfiguratorEntity|null $configuratorEntity */
  601.         $configuratorEntity $this->configuratorRepository->search($configuratorCriteria$context->getContext())->first();
  602.         if ($configuratorEntity == null) {
  603.             throw new RuntimeException("Configurator '$configuratorId' not found");
  604.         }
  605.         if ($savedConfigurationId != null) {
  606.             $existingCriteria = (new Criteria())
  607.                 ->setLimit(1)
  608.                 ->setIds([$savedConfigurationId])
  609.                 ->addAssociation("configurator")
  610.                 ->addAssociation("customer");
  611.             /** @var SavedConfigurationEntity|null $existingEntity */
  612.             $existingEntity $this->savedConfigurationRepository->search($existingCriteria$context->getContext())->first();
  613.         } else {
  614.             $existingEntity null;
  615.         }
  616.         if ($existingEntity != null) {
  617.             if ($existingEntity->getConfigurator()->getId() !== $configuratorEntity->getId()) {
  618.                 throw new RuntimeException("Different configurator!");
  619.             }
  620.             if ($existingEntity->getCustomer()->getId() !== $customer->getId()) {
  621.                 throw new RuntimeException("Different customer!");
  622.             }
  623.             if ($existingEntity->getWasOrdered()) {
  624.                 throw new RuntimeException("Has been ordered already!");
  625.             }
  626.             $this->savedConfigurationRepository->update([
  627.                 [
  628.                     "id" => $savedConfigurationId,
  629.                     "name" => $name,
  630.                     "layout" => $configuratorEntity->getLayout(),
  631.                     "configuration" => $configuration
  632.                 ]
  633.             ], $context->getContext());
  634.             $result = [
  635.                 "savedConfigurationId" => $savedConfigurationId,
  636.                 "savedConfigurationName" => $name
  637.             ];
  638.         } else {
  639.             $newId Uuid::randomHex();
  640.             $this->savedConfigurationRepository->create([
  641.                 [
  642.                     "id" => $newId,
  643.                     "configuratorId" => $configuratorId,
  644.                     "customerId" => $customer->getId(),
  645.                     "wasOrdered" => false,
  646.                     "name" => $name,
  647.                     "layout" => $configuratorEntity->getLayout(),
  648.                     "configuration" => $configuration
  649.                 ]
  650.             ], $context->getContext());
  651.             $result = [
  652.                 "savedConfigurationId" => $newId,
  653.                 "savedConfigurationName" => $name
  654.             ];
  655.         }
  656.         return new JsonResponse($result);
  657.     }
  658. }