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

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.                 "cover" => $layoutSection["cover"] ?? null,
  284.                 "coverDirection" => $layoutSection["coverDirection"] ?? "left",
  285.                 "hint" => $layoutSection["hint"] ?? null,
  286.                 "products" => [],
  287.                 "type" => $layoutSection["type"] ?? "product",
  288.                 "options" => $layoutSection["options"] ?? null,
  289.                 "contents" => $layoutSection["contents"] ?? [],
  290.                 "contentVerticalAlignment" => $layoutSection["contentVerticalAlignment"] ?? "top"
  291.             ];
  292.         }
  293.         return $sectionData;
  294.     }
  295.     private function orderProducts(array &$products) {
  296.         usort($products, function (array $productA, array $productB) {
  297.             return strcasecmp($productA["productNumber"], $productB["productNumber"]);
  298.         });
  299.     }
  300.     private function restExplode(string|array|null $valuebool $fixNumber false): array
  301.     {
  302.         if (is_array($value)) {
  303.             return $value;
  304.         }
  305.         if (empty($value)) {
  306.             return [];
  307.         }
  308.         if ($fixNumber && is_string($value) && preg_match("/^\d+\.\d+$/"trim($value)) === 1) {
  309.             // Assume tha excel changed a 2 element list into a number and try to fix it
  310.             $value str_replace("."","$value);
  311.         }
  312.         return array_filter(array_map("trim"explode(","$value)));
  313.     }
  314.     private function parsePreCondition(?string $preCondition): ?string {
  315.         if (empty($preCondition)) {
  316.             return null;
  317.         }
  318.         return preg_replace([
  319.             '/\s*\|\|\s*/',
  320.             '/\s*&&\s*/'
  321.         ], [
  322.             ' OR ',
  323.             ' AND '
  324.         ], $preCondition);
  325.     }
  326.     private function structureMatrixConfigTab(ConfiguratorEntity $configurator, array $layoutTabSalesChannelContext $context): array {
  327.         $tabId $layoutTab["id"];
  328.         $sectionData = isset($layoutTab["sections"]) ? $this->structureSections($layoutTab["sections"]) : [];
  329.         $products $configurator->getProducts();
  330.         $productData = [];
  331.         /** @var ProductEntity $product */
  332.         foreach ($products as $product) {
  333.             $data $product->getCustomFields();
  334.             if (empty($data) || (is_array($data) && count($data) <= 0)) {
  335.                 $criteria = new Criteria();
  336.                 $criteria->addFilter(new AndFilter([
  337.                         new EqualsFilter("configurator"$configurator->getId()),
  338.                         new EqualsFilter("productNumber"$product->getProductNumber())
  339.                     ]
  340.                 ));
  341.                 /** @var ConfiguratorProductCustomFieldEntity $configuratorProductCustomFields */
  342.                 $configuratorProductCustomFields $this->configuratorProductCustomFieldsRepository->search($criteria$context->getContext())->first();
  343.                 if ($configuratorProductCustomFields) {
  344.                     $data $configuratorProductCustomFields->getCustomFields();
  345.                     $product->setCustomFields($data);
  346.                 }
  347.             }
  348.             $productTabId $data["configurator_tab"] ?? null;
  349.             if ($productTabId === null || $productTabId !== $tabId) {
  350.                 continue; // doesnt belong to tab, skip
  351.             }
  352.             $productData[] = $this->buildProductData($productfalse$context);
  353.         }
  354.         $this->orderProducts($productData);
  355.         $layoutFields $layoutTab["config"]["fields"] ?? [];
  356.         $layoutMatrixType $layoutTab["config"]["type"] ?? null;
  357.         $layoutMatrix $layoutTab["config"]["matrix"] ?? [];
  358.         $fields = [];
  359.         foreach ($layoutFields as $layoutField) {
  360.             $field = [
  361.                 "id" => $layoutField["id"],
  362.                 "name" => $layoutField["title"],
  363.                 "type" => $layoutField["type"],
  364.                 "valueParent" => $layoutField["value_parent"] ?? null,
  365.                 "filterField" => $layoutField["filter_field"] ?? null,
  366.                 "requires" => $layoutField["requires"] ?? [],
  367.                 "valueFormat" => $layoutField["value_format"] ?? null,
  368.                 "values" => $layoutField["values"] ?? null,
  369.                 "productGroup" => $layoutField["product_group"] ?? null,
  370.                 "valueMeta" => $layoutField["value_meta"] ?? null,
  371.             ];
  372.             if ($field["id"] === "seat_width") {
  373.                 $seatDepthEntries = [];
  374.                 foreach ($field["values"] as $value) {
  375.                     $productNumber $value["article_number"] ?? null;
  376.                     if ($productNumber === null) {
  377.                         continue;
  378.                     }
  379.                     $seatWidthProduct $this->findProductInDataByProductNumber($productData$productNumber);
  380.                     if ($seatWidthProduct === null) {
  381.                         continue;
  382.                     }
  383.                     $seatDepths $seatWidthProduct["seatDepths"] ?? null;
  384.                     if (empty($seatDepths)) {
  385.                         continue;
  386.                     }
  387.                     $seatDepthEntries[$productNumber] = $seatDepths;
  388.                 }
  389.                 $field["seatDepths"] = $seatDepthEntries;
  390.             }
  391.             if ($layoutField["type"] === "seat-depth") {
  392.                 $field["minValue"] = $layoutField["min_value"] ?? null;
  393.                 $field["maxValue"] = $layoutField["max_value"] ?? null;
  394.                 $field["extraCostProduct"] = $layoutField["extra_cost_article"] ?? null;
  395.             }
  396.             $fields[] = $field;
  397.         }
  398.         if ($sectionData) {
  399.             // remove string keys
  400.             $sectionData array_values($sectionData);
  401.         }
  402.         $tabData = [
  403.             "id" => $tabId,
  404.             "name" => $layoutTab["title"] ?? $tabId,
  405.             "type" => "matrix_config",
  406.             "config" => [
  407.                 "name" => $layoutTab["config"]["title"] ?? "",
  408.                 "fields" => $fields,
  409.                 "type" => $layoutMatrixType,
  410.                 "matrix" => $layoutMatrix,
  411.                 "cover" => $layoutTab["config"]["cover"] ?? null
  412.             ],
  413.             "sections" => $sectionData,
  414.             "products" => $productData
  415.         ];
  416.         return [$tabId => $tabData];
  417.     }
  418.     /**
  419.      * @param string|null $countPerGroupRaw
  420.      * @return array 0=>$countPerGroup, 1=>$optionGroup
  421.      */
  422.     private function parseCountPerGroupAndOptionGroup(?string $countPerGroupRaw): array
  423.     {
  424.         if (empty($countPerGroupRaw)) {
  425.             return [nullnull];
  426.         } else {
  427.             // Format is "id:name:countPerGroup" or "id:countPerGroup"
  428.             if (preg_match("~^([^:\n]+):(?:([^:\n]+):)?([^:\n]+)$~"$countPerGroupRaw$optionGroupMatch) === 1) {
  429.                 $optionGroup = [
  430.                     "id" => $optionGroupMatch[1],
  431.                     "name" => $optionGroupMatch[2] ?: $optionGroupMatch[1], // fallback to id
  432.                     "countPerGroup" => $optionGroupMatch[3],
  433.                 ];
  434.                 // "Turn off" section validation by using "0,N"
  435.                 return [$optionGroupMatch[3], $optionGroup];
  436.             } else {
  437.                 return [$countPerGroupRawnull];
  438.             }
  439.         }
  440.     }
  441.     private function getThumbnail(SalesChannelContext $context, ?MediaEntity $mediaint $targetHeightint $targetWidth): ?string
  442.     {
  443.         if ($media == null) {
  444.             return null;
  445.         }
  446.         $bestMatch null;
  447.         $minDistance PHP_INT_MAX;
  448.         foreach ($media->getThumbnails() as $thumbnail) {
  449.             $distance abs($thumbnail->getWidth() - $targetWidth) + abs($thumbnail->getHeight() - $targetHeight);
  450.             if ($distance $minDistance) {
  451.                 $minDistance $distance;
  452.                 $bestMatch $thumbnail;
  453.             }
  454.         }
  455.         if ($bestMatch) {
  456.             return $bestMatch->getUrl();
  457.         } else {
  458.             return $media->getUrl();
  459.         }
  460.     }
  461.     function buildProductData (ProductEntity $productbool $canHideSalesChannelContext $context): array
  462.     {
  463.         $data $product->getCustomFields();
  464.         $seatWidths array_map("intval"$this->restExplode($data['compatible_seatwidth'] ?? [], true));
  465.         $compatibleProducts $this->restExplode($data['compatible_product'] ?? null);
  466.         $incompatibleProducts $this->restExplode($data['incompatible_product'] ?? null);
  467.         $requiredProducts $this->restExplode($data['required_product'] ?? null);
  468.         $preConditions $this->parsePreCondition($data['pre_condition'] ?? null);
  469.         $wheelSizes array_map("intval"$this->restExplode($data['wheel_sizes'] ?? [], true));
  470.         $backHeights array_map("intval"$data['back_height'] ?? []);
  471.         $casterSizes array_map("intval"$data['caster_sizes'] ?? []);
  472.         $seatHeights array_map("intval"$data['seat_heights'] ?? []);
  473.         $seatDepths array_map("intval"$data['seat_depths'] ?? []);
  474.         $dropdownValues $data["dropdown_values"] ?? null;
  475.         list($countPerGroup$optionGroup) = $this->parseCountPerGroupAndOptionGroup($data['count_per_group'] ?? null);
  476.         return [
  477.             "id" => $product->getId(),
  478.             "name" => $product->getTranslation("name"),
  479.             // TODO Use thumbnails?
  480.             "cover" => $this->getThumbnail(
  481.                 $context$product->getCover()?->getMedia(),
  482.                 200,
  483.                 200
  484.             ),
  485.             'productNumber' => $product->getProductNumber(),
  486.             "canHide" => $canHide,
  487.             'hint' => null// TODO
  488.             "productGroup" => $data['product_group'] ?? null,
  489.             'productDescription' => $data['product_description'] ?? null,
  490.             'displayType' => $data['display_type'] ?? null,
  491.             'displayValue' => $data['display_value'] ?? null,
  492.             "seatWidths" => $seatWidths,
  493.             // Don't output compatible products, since they are not used
  494.             // 'compatibleProducts' => $compatibleProducts,
  495.             'compatibleProducts' => [],
  496.             'incompatibleProducts' => $incompatibleProducts,
  497.             'requiredProducts' => $requiredProducts,
  498.             'countPerGroup' => $countPerGroup,
  499.             'optionGroup' => $optionGroup,
  500.             'outputOrder' => $data['output_order'] ?? null,
  501.             'configuratorTab' => $data['configurator_tab'] ?? null,
  502.             'configuratorTabSection' => $data['configurator_tab_section'] ?? null,
  503.             'preCondition' => $preConditions,
  504.             'wheelSizes' => $wheelSizes,
  505.             'requiredSelections' => $data['required_selection'] ?? [],
  506.             'backHeights' => $backHeights,
  507.             'casterSizes' => $casterSizes,
  508.             'seatHeights'=> $seatHeights,
  509.             'seatDepths' => $seatDepths,
  510.             'dropdownValues' => $dropdownValues,
  511.             'description' => $product->getDescription(),
  512.             'smallHint' => $data["small_hint"] ?? null,
  513.         ];
  514.     }
  515.     /**
  516.      * @Route("/hoggi-configurator/{configuratorId}", name="frontend.hoggi-configurator.page", methods={"GET"}, defaults={"_loginRequired"=true})
  517.      */
  518.     public function hoggiConfiguratorPage(Request $requestCart $cartSalesChannelContext $context): Response
  519.     {
  520.         $page $this->configuratorPageLoader->load($request$cart$context);
  521.         return $this->renderStorefront('@SwagHoggi/storefront/page/hoggi-configurator/index.html.twig', [
  522.             'page' => $page
  523.         ]);
  524.     }
  525.     /**
  526.      * @Route("/hoggi-configurator/configurations/list", name="frontend.hoggi-configurator.saved-configuration.list", methods={"GET"}, defaults={"_loginRequired"=true})
  527.      */
  528.     public function savedConfigurationListingPage(Request $requestSalesChannelContext $context): Response
  529.     {
  530.         $page $this->savedConfiguratorListingPageLoader->load($request$context);
  531.         return $this->renderStorefront('@SwagHoggi/storefront/page/hoggi-configurator/saved-configuration-listing.html.twig', [
  532.             'page' => $page
  533.         ]);
  534.     }
  535.     /**
  536.      * @Route("/hoggi-configurator/configurations/remove/{configurationId}", name="frontend.hoggi-configurator.saved-configuration.remove", methods={"GET"}, defaults={"_loginRequired"=true})
  537.      */
  538.     public function savedConfigurationRemovePage(Request $requestCart $cartSalesChannelContext $context): Response
  539.     {
  540.         $customer $context->getCustomer();
  541.         $configurationId $request->attributes->get("configurationId");
  542.         $configurationCriteria = (new Criteria())->setLimit(1)
  543.             ->setIds([$configurationId])
  544.             ->addFilter(new EqualsFilter("customerId"$customer->getId()));
  545.         $configurationEntity $this->savedConfigurationRepository->search($configurationCriteria$context->getContext())->first();
  546.         if (!$configurationEntity) {
  547.             $this->addFlash("danger""Die Konfiguration existiert nicht");
  548.         } else {
  549.             $this->savedConfigurationRepository->delete([
  550.                 [
  551.                     "id" => $configurationId
  552.                 ]
  553.             ], $context->getContext());
  554.             $this->addFlash("success""Die Konfiguration wurde gelöscht");
  555.         }
  556.         return $this->redirectToRoute('frontend.hoggi-configurator.saved-configuration.list');
  557.     }
  558.     private function findProductInDataByProductNumber(array $productDatastring $productNumber): ?array {
  559.         foreach ($productData as $product) {
  560.             if ($product["productNumber"] === $productNumber) {
  561.                 return $product;
  562.             }
  563.         }
  564.         return null;
  565.     }
  566.     /**
  567.      * @param Request $request
  568.      * @param SalesChannelContext $context Current context
  569.      * @return Response
  570.      * @Route("/hoggi-configurator/save-configuration", name="frontend.hoggi-configurator.save-configuration", methods={"POST"}, defaults={"csrf_protected"=false, "XmlHttpRequest"=true})
  571.      */
  572.     public function saveConfiguration(Request $requestSalesChannelContext $context): Response {
  573.         /** @var string $name */
  574.         $name $request->request->get("name");
  575.         /** @var string|null $savedConfigurationId */
  576.         $savedConfigurationId $request->request->get("savedConfigurationId");
  577.         /** @var array $configuration */
  578.         $configuration $request->request->get("configurator") ?: [];
  579.         /** @var string $configuratorId */
  580.         $configuratorId $request->request->get("configuratorId");
  581.         /** @var CustomerEntity|null $customer */
  582.         $customer $context->getCustomer();
  583.         if (empty($name)) {
  584.             $name "Unbekannt";
  585.         }
  586.         if (empty($configuration)) {
  587.             throw new RuntimeException("No configuration data!");
  588.         }
  589.         if (empty($configuratorId)) {
  590.             throw new RuntimeException("No configurator id!");
  591.         }
  592.         if ($customer == null) {
  593.             throw new RuntimeException("Not logged in!");
  594.         }
  595.         $configuratorCriteria = (new Criteria([$configuratorId]))->setLimit(1);
  596.         /** @var ConfiguratorEntity|null $configuratorEntity */
  597.         $configuratorEntity $this->configuratorRepository->search($configuratorCriteria$context->getContext())->first();
  598.         if ($configuratorEntity == null) {
  599.             throw new RuntimeException("Configurator '$configuratorId' not found");
  600.         }
  601.         if ($savedConfigurationId != null) {
  602.             $existingCriteria = (new Criteria())
  603.                 ->setLimit(1)
  604.                 ->setIds([$savedConfigurationId])
  605.                 ->addAssociation("configurator")
  606.                 ->addAssociation("customer");
  607.             /** @var SavedConfigurationEntity|null $existingEntity */
  608.             $existingEntity $this->savedConfigurationRepository->search($existingCriteria$context->getContext())->first();
  609.         } else {
  610.             $existingEntity null;
  611.         }
  612.         if ($existingEntity != null) {
  613.             if ($existingEntity->getConfigurator()->getId() !== $configuratorEntity->getId()) {
  614.                 throw new RuntimeException("Different configurator!");
  615.             }
  616.             if ($existingEntity->getCustomer()->getId() !== $customer->getId()) {
  617.                 throw new RuntimeException("Different customer!");
  618.             }
  619.             if ($existingEntity->getWasOrdered()) {
  620.                 throw new RuntimeException("Has been ordered already!");
  621.             }
  622.             $this->savedConfigurationRepository->update([
  623.                 [
  624.                     "id" => $savedConfigurationId,
  625.                     "name" => $name,
  626.                     "layout" => $configuratorEntity->getLayout(),
  627.                     "configuration" => $configuration
  628.                 ]
  629.             ], $context->getContext());
  630.             $result = [
  631.                 "savedConfigurationId" => $savedConfigurationId,
  632.                 "savedConfigurationName" => $name
  633.             ];
  634.         } else {
  635.             $newId Uuid::randomHex();
  636.             $this->savedConfigurationRepository->create([
  637.                 [
  638.                     "id" => $newId,
  639.                     "configuratorId" => $configuratorId,
  640.                     "customerId" => $customer->getId(),
  641.                     "wasOrdered" => false,
  642.                     "name" => $name,
  643.                     "layout" => $configuratorEntity->getLayout(),
  644.                     "configuration" => $configuration
  645.                 ]
  646.             ], $context->getContext());
  647.             $result = [
  648.                 "savedConfigurationId" => $newId,
  649.                 "savedConfigurationName" => $name
  650.             ];
  651.         }
  652.         return new JsonResponse($result);
  653.     }
  654. }