src/Elements/Bundle/DemiFrontendBundle/Controller/AdditionalServiceController.php line 264

Open in your IDE?
  1. <?php
  2. /**
  3.  * Elements DeMI
  4.  *
  5.  * This source file is available under the elements DeMI license version 1
  6.  *
  7.  *  @copyright  Copyright (c) elements.at New Media Solutions GmbH (https://www.elements.at/)
  8.  *  @license    elements DeMI Lizenz Version 1 (https://www.elements.at/de/demi-lizenz)
  9.  */
  10. namespace Elements\Bundle\DemiFrontendBundle\Controller;
  11. use Carbon\Carbon;
  12. use Carbon\Traits\Creator;
  13. use Elements\Bundle\CmsToolsBundle\Tool\Helper\ElementsCustomDateFormat;
  14. use Elements\Bundle\DemiFrontendBundle\Event\DemiDirectInquiryEvent;
  15. use Elements\Bundle\DemiFrontendBundle\Service\AdditionalService;
  16. use Elements\Bundle\DemiFrontendBundle\Service\CartItemInfo;
  17. use Elements\Demi\Frontend\Service\Configuration;
  18. use Elements\Bundle\DemiFrontendBundle\Service\ConnectedEntryProvider;
  19. use Elements\Bundle\DemiFrontendBundle\Service\EcommerceHelper;
  20. use Elements\Bundle\DemiFrontendBundle\Service\PaginatorHelper;
  21. use Elements\Bundle\DemiFrontendBundle\Service\Payment;
  22. use Elements\Demi\AdditionalService\Search\Listing;
  23. use Elements\Demi\AdditionalService\Search\Parameter;
  24. use Elements\Demi\AdditionalService\Search\Parameter\Line;
  25. use Elements\Demi\Deskline\AdditionalService\Search\Adapter;
  26. use Elements\Demi\Deskline\Config;
  27. use Elements\Demi\Deskline\Constant\AddressInterface;
  28. use Elements\Demi\Deskline\Constant\DescriptionInterface;
  29. use Elements\Demi\Deskline\Constant\FacilityGroupTypeInterface;
  30. use Elements\Demi\Deskline\Service\Search\CancellationInformation;
  31. use Elements\Demi\Info\CartItemInfoHolder;
  32. use Elements\Demi\Model\AdditionalProduct;
  33. use Elements\Demi\Model\AdditionalServiceProvider;
  34. use ErrorException;
  35. use Exception;
  36. use InvalidArgumentException;
  37. use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
  38. use Pimcore\Bundle\EcommerceFrameworkBundle\Tracking\TrackingManager;
  39. use Pimcore\Db;
  40. use Pimcore\Model\DataObject;
  41. use Pimcore\Model\DataObject\AbstractObject;
  42. use Pimcore\Model\DataObject\DemiAdditionalService;
  43. use Pimcore\Model\DataObject\DemiFilterObject;
  44. use Pimcore\Model\Document;
  45. use Pimcore\Model\Document\Editable\Areablock;
  46. use Symfony\Component\HttpFoundation\JsonResponse;
  47. use Symfony\Component\HttpFoundation\Request;
  48. use Symfony\Component\HttpFoundation\Response;
  49. use Symfony\Component\HttpKernel\Exception\GoneHttpException;
  50. use Symfony\Component\Security\Core\User\UserInterface;
  51. use Symfony\Component\DependencyInjection\ContainerInterface;
  52. use Symfony\Component\Validator\Constraints\Email;
  53. use Symfony\Component\Validator\Validator\ValidatorInterface;
  54. class AdditionalServiceController extends AbstractDemiController
  55. {
  56.     public function __construct(protected EcommerceHelper $ecommerceHelper,
  57.                                 protected CartItemInfo $cartItemInfoService,
  58.                                 protected Payment $paymentHelper,
  59.                                 protected array $enabledFeatures,
  60.                                 protected ConnectedEntryProvider $connectedEntryProvider,
  61.                                 protected Listing $additionalServiceListingLive,
  62.                                 protected Listing $additionalServiceListingLocal,
  63.                                 protected TrackingManager $trackingManager,
  64.                                 protected AdditionalService $additionalServiceService,
  65.                                 Configuration $config,
  66.                                 protected PaginatorHelper $paginatorHelper,
  67.                                 protected Adapter $adapter,
  68.                                 ElementsCustomDateFormat $elementsCustomDateFormat,
  69.                                 ContainerInterface $container,
  70.                                 protected SavingRequestController $savingRequestController,
  71.                                 protected ?ValidatorInterface $emailValidator null)
  72.     {
  73.         $this->setElementsCustomDateFormat($elementsCustomDateFormat);
  74.         $this->setConfiguration($config);
  75.         $this->setContainer($container);
  76.         if ($emailValidator === null) {
  77.             $this->emailValidator $this->container->get('validator.email');
  78.         }
  79.     }
  80.     public function overviewAction(Request $request) : Response
  81.     {
  82.         if (!$this->enabledFeatures['additionalServices']) {
  83.             throw new Exception('AdditionalServices are not enabled!');
  84.         }
  85.         $editablePrefix $this->findEditablePrefix($this->document);
  86.         $providerId DataObject\DemiAdditionalServiceProvider::classId();
  87.         $productId DataObject\DemiAdditionalProduct::classId();
  88.         $items = new DemiAdditionalService\Listing();
  89.         $serviceId $items->getClassId();
  90.         $lang $request->getLocale();
  91.         $dateBegin htmlentities($request->get('from'));
  92.         if (empty($dateBegin)) {
  93.             $dateBegin = new Carbon();
  94.             $dateBegin->startOfDay();
  95.         } else {
  96.             $dateBegin $this->elementsCustomDateFormat->stringToCarbon($dateBegin);
  97.         }
  98.         $dateEnd htmlentities($request->get('to'));
  99.         if (empty($dateEnd)) {
  100.             $dateEnd $dateBegin->copy()->addYear();
  101.         } else {
  102.             $dateEnd $this->elementsCustomDateFormat->stringToCarbon($dateEnd);
  103.         }
  104.         $text $request->get('keyword');
  105.         if (!empty($text)) {
  106.             $items->addConditionParam(Db::get()->quoteIdentifier('name') . ' LIKE ' Db::get()->quote('%' $text '%'));
  107.             $viewParams["text"] = htmlentities(nl2br($text));
  108.         }
  109.         $selectedHolidayTheme $request->get('category');
  110.         $predefinedHolidayThemes $this->document->getEditable($editablePrefix 'holidayThemes')->getData();
  111.         if (!empty($selectedHolidayTheme)) {
  112.             $obj AbstractObject::getById($selectedHolidayTheme);
  113.             if ($obj instanceof DataObject\DemiFilterObject) {
  114.                 foreach ($obj->getFilterElements() as $theme) {
  115.                     $themeids[] = $theme->getId();
  116.                 }
  117.             } else {
  118.                 $themeids[] = $selectedHolidayTheme;
  119.             }
  120.             $items->addConditionParam("object_localized_{$serviceId}_{$lang}.o_id in (select o_parentId from object_$productId where object_$productId.o_id in (select src_id from object_relations_$productId where dest_id in ("implode(','$themeids) . ") and fieldname = 'holidaythemes'))");
  121.         } elseif (!empty($predefinedHolidayThemes)) {
  122.             $themeids = [];
  123.             foreach ($predefinedHolidayThemes as $holidayTheme) {
  124.                 $obj AbstractObject::getById($holidayTheme);
  125.                 if ($obj instanceof DataObject\DemiFilterObject) {
  126.                     foreach ($obj->getFilterElements() as $item) {
  127.                         $themeids[] = $item->getId();
  128.                     }
  129.                 } else {
  130.                     $themeids[] = $holidayTheme->getId();
  131.                 }
  132.             }
  133.             $items->addConditionParam("object_localized_{$serviceId}_{$lang}.o_id in (select o_parentId from object_$productId where object_$productId.o_id in (select src_id from object_relations_$productId where dest_id in ("implode(','$themeids).") and fieldname = 'holidaythemes'))");
  134.         }
  135.         $selectedRegion $request->get('region');
  136.         $predefinedRegions $this->document->getEditable($editablePrefix.'regions')->getData();
  137.         if (!empty($selectedRegion)) {
  138.             $townids = [];
  139.             $districtIds = [];
  140.             $region AbstractObject::getById($selectedRegion);
  141.             if ($region instanceof DemiFilterObject) {
  142.                 foreach ($region->getFilterElements() as $filterElement) {
  143.                     if ($filterElement instanceof DataObject\DemiRegion) {
  144.                         foreach ($filterElement->getChildren() as $town) {
  145.                             $townids[] = $town->getId();
  146.                         }
  147.                     } elseif ($filterElement instanceof DataObject\DemiTown) {
  148.                         $townids[] = $filterElement->getId();
  149.                     } elseif ($filterElement instanceof DataObject\DemiDistrict) {
  150.                         $districtIds[] = $filterElement->getId();
  151.                     }
  152.                 }
  153.             } elseif ($region instanceof DataObject\DemiRegion) {
  154.                 foreach ($region->getChildren() as $town) {
  155.                     $townids[] = $town->getId();
  156.                 }
  157.             } elseif ($region instanceof DataObject\DemiTown) {
  158.                 $townids[] = $region->getId();
  159.             } elseif ($region instanceof DataObject\DemiDistrict) {
  160.                 $districtIds[] = $region->getId();
  161.             }
  162.         } elseif (!empty($predefinedRegions)) {
  163.             foreach ($predefinedRegions as $predRegion) {
  164.                 if (!$predRegion instanceof AbstractObject) {
  165.                     $region AbstractObject::getById($predRegion);
  166.                 } else {
  167.                     $region $predRegion;
  168.                 }
  169.                 if ($region instanceof DemiFilterObject) {
  170.                     foreach ($region->getFilterElements() as $filterElement) {
  171.                         if ($filterElement instanceof DataObject\DemiRegion) {
  172.                             foreach ($filterElement->getChildren() as $town) {
  173.                                 $townids[] = $town->getId();
  174.                             }
  175.                         } elseif ($filterElement instanceof DataObject\DemiTown) {
  176.                             $townids[] = $filterElement->getId();
  177.                         } elseif ($filterElement instanceof DataObject\DemiDistrict) {
  178.                             $districtIds[] = $filterElement->getId();
  179.                         }
  180.                     }
  181.                 } elseif ($region instanceof DataObject\DemiRegion) {
  182.                     foreach ($region->getChildren() as $town) {
  183.                         $townids[] = $town->getId();
  184.                     }
  185.                 } elseif ($region instanceof DataObject\DemiTown) {
  186.                     $townids[] = $region->getId();
  187.                 } elseif ($region instanceof DataObject\DemiDistrict) {
  188.                     $districtIds[] = $region->getId();
  189.                 }
  190.             }
  191.         }
  192.         $selectedServiceTypes $request->get('serviceType');
  193.         $serviceTypeIds = [];
  194.         $predefinedServiceTypes $this->document->getEditable($editablePrefix'additionalServiceTypes')->getData();
  195.         if (!empty($selectedServiceTypes)) {
  196.             if (is_array($selectedServiceTypes)) {
  197.                 foreach ($selectedServiceTypes as $selectedServiceType) {
  198.                     $obj DataObject\DemiAdditionalServiceType::getById($selectedServiceType);
  199.                     if ($obj instanceof DataObject\DemiAdditionalServiceType) {
  200.                         $serviceTypeIds[] = $selectedServiceType;
  201.                     }
  202.                 }
  203.             } else {
  204.                 $obj DataObject\DemiAdditionalServiceType::getById($selectedServiceTypes);
  205.                 if ($obj instanceof DataObject\DemiAdditionalServiceType) {
  206.                     $serviceTypeIds[] = $selectedServiceTypes;
  207.                 }
  208.             }
  209.             $items->addConditionParam("object_localized_{$serviceId}_{$lang}.o_id in (select src_id from object_relations_$serviceId where dest_id in (" implode(','$serviceTypeIds) . '))');
  210.         } elseif (!empty($predefinedServiceTypes)) {
  211.             foreach ($predefinedServiceTypes as $serviceType) {
  212.                 $allServiceTypes[] = $serviceType->getId();
  213.             }
  214.             $items->addConditionParam("object_localized_{$items->getClassId()}_{$lang}.o_id in (select src_id from object_relations_$serviceId where dest_id in (" implode(','$allServiceTypes) . '))');
  215.         }
  216.         if (!empty($townids)) {
  217.             $items->addConditionParam("o_parentId in (select o_id from object_$providerId where town__id in (" implode(','$townids) . ') AND o_published = 1)');
  218.         } elseif (!empty($districtIds)) {
  219.             $items->addConditionParam("o_parentId in (select o_id from object_$providerId where district__id in (" implode(','$districtIds) . ') AND o_published = 1)');
  220.         } else {
  221.             $items->addConditionParam("o_parentId in (select o_id from object_$providerId where o_published = 1)");
  222.         }
  223.         $items->addConditionParam('needAcco is null or needAcco = 0');
  224.         $items->addConditionParam("object_localized_{$serviceId}_{$lang}.o_id in (select o_parentid from object_$productId where packageOnly is null or packageOnly = 0)");
  225.         $items->addConditionParam("object_localized_{$serviceId}_{$lang}.o_id in (select o_id from object_collection_demiDate_$serviceId where (object_collection_demiDate_$serviceId.to > {$dateBegin->getTimestamp()} and object_collection_demiDate_$serviceId.from < {$dateEnd->getTimestamp()}))");
  226.         $items $this->additionalServiceService->sortListingPerDefault($items$serviceId$lang);
  227.         $items $this->additionalServiceService->modifyListingForOverview($items);
  228.         $perPage $this->document->getEditable($editablePrefix.'perPage')->getData() ?: 12;
  229.         $paginator $this->paginatorHelper->getPaginator($items$perPage$request->get('page'1), 5);
  230.         $viewParams['paginator'] = $paginator;
  231.         $viewParams['isAjax'] = $request->isXmlHttpRequest();
  232.         $viewParams['additionalServiceNoResultText'] = $this->configuration->getAdditionalServiceNoResultText();
  233.         $viewParams['currencySymbol'] = $this->paymentHelper->getCurrency();
  234.         if ($request->isXmlHttpRequest() && ($request->headers->get('format') === 'json')) {
  235.             $response = [
  236.                 'status' => 'success',
  237.                 'html' => $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/list-result.html.twig'$viewParams)->getContent()
  238.             ];
  239.             return $this->json($response);
  240.         }
  241.         return $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/list-result.html.twig'$viewParams);
  242.     }
  243.     public function detailAction(Request $requestint $idUserInterface $user null): ?Response
  244.     {
  245.         if (!$this->enabledFeatures['additionalServices']) {
  246.             throw new ErrorException('AdditionalServices are not enabled!');
  247.         }
  248.         $today = new Carbon();
  249.         $productId DataObject\DemiAdditionalProduct::classId();
  250.         $providerId DataObject\DemiAdditionalServiceProvider::classId();
  251.         $object DataObject::getById($request->get('id'));
  252.         if (!$object instanceof DemiAdditionalService) {
  253.             throw new GoneHttpException("The requested Object doesn't exist (anymore)");
  254.         }
  255.         if (!$object->isPublished()) {
  256.             return $this->forward(self::class. '::unavailableAction', [
  257.                 'request' => $request
  258.             ]);
  259.         }
  260.         if (empty($object->getAdditionalProducts())) {
  261.             throw new GoneHttpException('The requested Object does not offer any services!');
  262.         }
  263.         $serviceProvider $object->getParent() instanceof AdditionalServiceProvider $object->getParent() : null;
  264.         if (!$serviceProvider instanceof AdditionalServiceProvider || !$serviceProvider->isPublished()) {
  265.             throw new GoneHttpException("The requested Object doesn't have a published Serviceprovider!");
  266.         }
  267.         $viewParams['user'] = $user;
  268.         if ($request->get('enquire')) {
  269.             $success $this->sendEnquire($request$object$user);
  270.             if ($success) {
  271.                 $this->addResponseHeader('X-Robots-Tag''noindex,nofollow');
  272.                 $response = [
  273.                     'status' => 'success',
  274.                     'html' => $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/enquiry-success.html.twig', [])->getContent()
  275.                 ];
  276.                 return $this->json($response);
  277.             } else {
  278.                 throw new \Elements\Demi\Deskline\Exception('can not sent enquiry - dsi saving request was not successful');
  279.             }
  280.         }
  281.         if ($object->getDates()) {
  282.             $endDate null;
  283.             foreach ($object->getDates()->getItems() as $item) {
  284.                 if ($endDate === null) {
  285.                     $endDate $item->getTo();
  286.                 } elseif ($endDate->lt($item->getTo())) {
  287.                     $endDate $item->getTo();
  288.                 }
  289.             }
  290.             if ($endDate !== null) {
  291.                 $endDate $endDate->endOfDay()->toRfc850String();
  292.                 $this->addResponseHeader('X-Robots-Tag''unavailable_after: ' $endDate);
  293.             }
  294.         }
  295.         $onlyHolidaythemes = new DemiAdditionalService\Listing();
  296.         $serviceId $onlyHolidaythemes->getClassId();
  297.         $themeids = [];
  298.         foreach ($object->getAdditionalProducts() as $product) {
  299.             foreach ($product->getHolidayThemes() as $holidayTheme) {
  300.                 $themeId $holidayTheme->getId();
  301.                 if (in_array($themeId$themeidstrue)) {
  302.                     continue;
  303.                 }
  304.                 $themeids[] = $themeId;
  305.             }
  306.         }
  307.         $onlyHolidaythemes->addConditionParam("o_id in (select o_parentId from object_$productId where holidayThemes like '%".implode(','$themeids)."%')");
  308.         $onlyHolidaythemes->addConditionParam('needAcco is null or needAcco = 0');
  309.         $onlyHolidaythemes->addConditionParam("o_id in (select o_id from object_collection_demiDate_$serviceId where object_collection_demiDate_$serviceId.from > {$today->getTimestamp()} or object_collection_demiDate_$serviceId.to > {$today->getTimestamp()})");
  310.         $onlyHolidaythemes->addConditionParam('o_id != '$object->getId());
  311.         $onlyHolidaythemes->addConditionParam("o_parentId in (select o_id from object_$providerId where o_published = 1 )");
  312.         $onlyHolidaythemes->addConditionParam("o_id in (select o_parentid from object_$productId where packageOnly is null or packageOnly = 0)");
  313.         $onlyHolidaythemes->setOrderKey('order');
  314.         $onlyHolidaythemes $this->additionalServiceService->modifyListingForOverview($onlyHolidaythemes);
  315.         $onlyHolidaythemes->setLimit(4);
  316.         $relatedItems $onlyHolidaythemes->load();
  317.         if (count($relatedItems) < 4) {
  318.             $townid $serviceProvider->getTown() ? $serviceProvider->getTown()->getId() : null;
  319.             $relatedServices = new DemiAdditionalService\Listing();
  320.             $relatedServices->addConditionParam('needAcco is null or needAcco = 0');
  321.             $relatedServices->addConditionParam("o_id in (select o_id from object_collection_demiDate_$serviceId where object_collection_demiDate_$serviceId.from > {$today->getTimestamp()} or object_collection_demiDate_$serviceId.to > {$today->getTimestamp()})");
  322.             $relatedServices->addConditionParam('o_id != '$object->getId());
  323.             if ($townid) {
  324.                 $relatedServices->addConditionParam("o_parentId in (select o_id from object_$providerId where town__id in ($townid) and o_published = 1 )");
  325.             } else {
  326.                 $relatedServices->addConditionParam("o_parentId in (select o_id from object_$providerId where o_published = 1 )");
  327.             }
  328.             if ($onlyHolidaythemes->count() > 0) {
  329.                 $alreadyLoadedIds = [];
  330.                 foreach ($relatedItems as $onlyHolidaytheme) {
  331.                     $alreadyLoadedIds[] = $onlyHolidaytheme->getId();
  332.                 }
  333.                 $relatedServices->addConditionParam('o_id not in (' implode(', '$alreadyLoadedIds). ')');
  334.             }
  335.             $relatedServices->addConditionParam("o_id in (select o_parentid from object_$productId where packageOnly is null or packageOnly = 0)");
  336.             $relatedServices->setOrderKey('order');
  337.             $relatedServices $this->additionalServiceService->modifyListingForOverview($relatedServices);
  338.             $relatedServices->setLimit(count($relatedItems));
  339.             $relatedItems array_merge($relatedItems$relatedServices->load());
  340.         }
  341.         $objectAddress $serviceProvider->getAddress([AddressInterface::ADDRESS_OBJECT]);
  342.         if ($objectAddress &&
  343.             ($this->configuration->getAdditionalServiceShowAddressObjectName()
  344.                 || $this->configuration->getAdditionalServiceShowAddressObjectAddress()
  345.                 || $this->configuration->getAdditionalServiceShowAddressObjectEmail()
  346.                 || $this->configuration->getAdditionalServiceShowAddressObjectPhone()
  347.                 || $this->configuration->getAdditionalServiceShowAddressObjectWebsite())
  348.         ) {
  349.             $viewParams["addressObject"] = $objectAddress;
  350.         }
  351.         $ownerAddress $serviceProvider->getAddress([AddressInterface::ADDRESS_OWNER]);
  352.         if ($ownerAddress &&
  353.             ($this->configuration->getAdditionalServiceShowAddressOwnerName()
  354.                 || $this->configuration->getAdditionalServiceShowAddressOwnerAddress()
  355.                 || $this->configuration->getAdditionalServiceShowAddressOwnerEmail()
  356.                 || $this->configuration->getAdditionalServiceShowAddressOwnerPhone()
  357.                 || $this->configuration->getAdditionalServiceShowAddressOwnerWebsite())
  358.         ) {
  359.             $viewParams["addressOwner"] = $ownerAddress;
  360.         }
  361.         // Facilities
  362.         $facilities $object->getFacilities();
  363.         $sortedHealthMeasures = [];
  364.         if ($facilities !== null) {
  365.             foreach ($facilities as $item) {
  366.                 $facility $item->getFacility();
  367.                 if ($facility) {
  368.                     $facGroup $facility->getFacilityGroup();
  369.                     if ($facGroup && $facGroup->getFacilityGroupType() == FacilityGroupTypeInterface::FACILITY_GROUP_TYPE_HEALTH) {
  370.                         if (!isset($sortedHealthMeasures[$facGroup->getId()])) {
  371.                             $data = [
  372.                                 'name' => $facGroup->getName(),
  373.                                 'keyname' => $facGroup->getDemiIconClass(),
  374.                                 'facilities' => []
  375.                             ];
  376.                             $sortedHealthMeasures[$facGroup->getId()] = $data;
  377.                         }
  378.                         $facName $facility->getName();
  379.                         $sortedHealthMeasures[$facGroup->getId()]['facilities'][] = $facName;
  380.                     }
  381.                 }
  382.             }
  383.         }
  384.         $viewParams["sortedHealthMeasures"] = $sortedHealthMeasures;
  385.         $viewParams["connectedEntries"] = $this->connectedEntryProvider->getConnectedEntriesForAdditional($object$request);
  386.         $viewParams["offeritem"] = $object;
  387.         $viewParams["loadAdditionalServiceJs"] = true;
  388.         $viewParams["relatedItems"] = $relatedItems;
  389.         return $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/detail.html.twig'$viewParams);
  390.     }
  391.     public static function sortAdditionalServiceProducts(AdditionalProduct $aAdditionalProduct $b): int
  392.     {
  393.         return (int) $a->getOrder() - (int) $b->getOrder();
  394.     }
  395.     public function productsAction(Request $request): Response
  396.     {
  397.         $serviceId $request->get('id');
  398.         $additionalService AbstractObject::getById((int) $serviceId);
  399.         /*
  400.         $dateFrom = $request->get('from') ? $this->elementsCustomDateFormat->stringToCarbon($request->get('from')) : new Carbon();
  401.         $dateTo = $request->get('to') ? $this->elementsCustomDateFormat->stringToCarbon($request->get('to')) : new Carbon();
  402. */
  403.         if($request->get('date')){
  404.             $date $this->elementsCustomDateFormat->stringToCarbon($request->get('date'));
  405.         } else if($request->get('earliestServiceDate')){
  406.             $date $this->elementsCustomDateFormat->stringToCarbon($request->get('earliestServiceDate'));
  407.         } else {
  408.             $date = new Carbon();
  409.         }
  410.         $date $request->get('date') ? $this->elementsCustomDateFormat->stringToCarbon($request->get('date')) : new Carbon();
  411.         $dateFrom=$date;
  412.         $dateTo= clone $dateFrom;
  413.         if($additionalService !== null){
  414.             $first true;
  415.             foreach($additionalService->getDates() as $availableDate){
  416.                 if($availableDate->getTo()->gt($date) && $date->lt($availableDate->getFrom())){
  417.                     //set date to first possibly available date
  418.                     if($first || $availableDate->getFrom()->lt($dateFrom)){
  419.                         $dateFrom $availableDate->getFrom();
  420.                     }
  421.                 }
  422.                 if ($availableDate->getTo()->gt($dateTo)) {
  423.                     $dateTo $availableDate->getTo();
  424.                 }
  425.                 $first false;
  426.             }
  427.         }
  428.         if(empty($request->get('date'))){
  429.            //no date given, use now + 1 year
  430.             $dateTo->addYear();
  431.         }
  432.         $period $dateFrom->diffInDays($dateTo) + 1;
  433.         if ($period 999) {
  434.             $period 365;
  435.             $dateTo $dateFrom->clone()->addYear();
  436.         }
  437.         $viewParams = [];
  438.         $products = [];
  439.         $allCalendarProducts = [];
  440.         if ($additionalService !== null) {
  441.             if ($additionalService && $additionalService->getParent() && $additionalService->getParent() instanceof AdditionalServiceProvider) {
  442.                 Config::setClientKeyOverrideFromObject($additionalService->getParent());
  443.             }
  444.             $productsAdditionalService $additionalService->getAdditionalProducts();
  445.             usort($productsAdditionalService, [__CLASS__'sortAdditionalServiceProducts']);
  446.             $additionalServicesList $this->additionalServiceListingLive;
  447.             $additionalServicesList->setAdapter($this->adapter);
  448.             foreach ($productsAdditionalService as $product) {
  449.                 $paramLines = new Line();
  450.                 $today = new Carbon();
  451.                 $aYearLater = (clone  $today)->addYear();
  452.                 $paramLines->setDateFrom($today);
  453.                 $paramLines->setDateTo($aYearLater);
  454.                 $paramLines->setBookOnly(false);
  455.                 $period $today->diffInDays($aYearLater) + 1;
  456.                 $paramLines->setPeriod($period);
  457.                 $paramLines->setProductId($product->getId());
  458.                 $params = new Parameter();
  459.                 $params->setSearchLines([$paramLines]);
  460.                 $params->setPage(1);
  461.                 $params->setPerPage(100);
  462.                 $additionalServicesList->setParameter($params);
  463.                 $allCalendarProducts += $additionalServicesList->getItems(0100);
  464.                 $additionalServicesList->flushObjects();
  465.             }
  466.             $availableDates = [];
  467.             $customDateFormat = new ElementsCustomDateFormat();
  468.             $firstAvailableDate null;
  469.             foreach($allCalendarProducts as $productId => $product){
  470.                 foreach($product->getPrices() as $price){
  471.                     if($price->getAvailability() > 0){
  472.                         $availableDates[$price->getStartDay()->getTimestamp()]=$customDateFormat->dateToString($price->getStartDay());
  473.                         if($firstAvailableDate === null || $price->getStartDay()->getTimestamp() < $firstAvailableDate->getTimestamp()){
  474.                             $firstAvailableDate = clone $price->getStartDay();
  475.                         }
  476.                     }
  477.                 }
  478.             }
  479.             ksort($availableDates);
  480.             $availableDates array_unique($availableDates);
  481.             $firstAvailableDate count($availableDates)>$customDateFormat->stringToCarbon(reset($availableDates)) : null;
  482.             // some sanity checks
  483.             if ($dateTo->lt($firstAvailableDate)) {
  484.                 $dateTo $firstAvailableDate;
  485.             }
  486.             if ($date->lt($firstAvailableDate)) {
  487.                 $date $firstAvailableDate;
  488.             }
  489.             // get products after the from and to sanity checks
  490.             foreach ($productsAdditionalService as $product) {
  491.                 $paramLines = new Line();
  492.                 $paramLines->setDateFrom($dateFrom);
  493.                 $paramLines->setDateTo($dateTo);
  494.                 $paramLines->setPeriod($period);
  495.                 $paramLines->setProductId($product->getId());
  496.                 $paramLines->setBookOnly(false);
  497.                 $params = new Parameter();
  498.                 $params->setBookOnly(false);
  499.                 $params->setSearchLines([$paramLines]);
  500.                 $params->setPage(1);
  501.                 $params->setPerPage(100);
  502.                 $additionalServicesList->setParameter($params);
  503.                 $products += $additionalServicesList->getItems(0100);
  504.                 $additionalServicesList->flushObjects();
  505.             }
  506.             $viewParams["products"] = $products;
  507.             $viewParams["from"] = $firstAvailableDate ?: $dateFrom;
  508.             $viewParams["to"] = $dateTo;
  509.             $viewParams["date"] = $request->get('date') ? $date $firstAvailableDate;
  510.             $viewParams["service"] = $additionalService;
  511.             //if ($request->headers->get('format') === 'json') {
  512.                 $viewParams["isNew"] = true;
  513.                 $viewParams["isAdditionalService"] = true;
  514.                 $viewParams["styleModifier"] = 'package-group--additionalservice';
  515.                 $viewParams["availableDates"] = array_values($availableDates);
  516.                 $response = [
  517.                     'status' => 'success',
  518.                     'html' => $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/products.html.twig'$viewParams)->getContent()
  519.                 ];
  520.                 return $this->json($response);
  521.             //}
  522.         }
  523.         return $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/products.html.twig'$viewParams);
  524.     }
  525.     public function productsAjaxAction(Request $request): ?JsonResponse
  526.     {
  527.         $reqProd $request->get('products');
  528.         $prodid key($reqProd);
  529.         $dateFrom $request->get('from') ? $this->elementsCustomDateFormat->stringToCarbon($request->get('from')) : new Carbon();
  530.         $dateTo $request->get('to') ? $this->elementsCustomDateFormat->stringToCarbon($request->get('to')) : new Carbon();
  531.         $serviceId $request->get('id');
  532.         $dateDiff $dateFrom->diff($dateTo);
  533.         $period = (int) $dateDiff->format('%R%a days') + 1;
  534.         $products = [];
  535.         if ($serviceId && AbstractObject::getById((int) $serviceId)) {
  536.             $additionalService AbstractObject::getById((int) $serviceId);
  537.             if ($additionalService && $additionalService->getParent() && $additionalService->getParent() instanceof AdditionalServiceProvider) {
  538.                 Config::setClientKeyOverrideFromObject($additionalService->getParent());
  539.             }
  540.             $productsAdditionalService $additionalService->getAdditionalProducts();
  541.             usort($productsAdditionalService, [__CLASS__'sortAdditionalServiceProducts']);
  542.             $additionalServicesList $this->additionalServiceListingLive;
  543.             $additionalServicesList->setAdapter($this->adapter);
  544.             foreach ($productsAdditionalService as $product) {
  545.                 if ($product->getId() !== $prodid) {
  546.                     continue;
  547.                 }
  548.                 $paramLines = new Line();
  549.                 $paramLines->setDateFrom($dateFrom);
  550.                 $paramLines->setDateTo($dateTo);
  551.                 $paramLines->setPeriod($period);
  552.                 $paramLines->setProductId($product->getId());
  553.                 $paramLines->setBookOnly(false);
  554.                 $params = new Parameter();
  555.                 $params->setBookOnly(false);
  556.                 $params->setSearchLines([$paramLines]);
  557.                 $params->setPage(1);
  558.                 $params->setPerPage(100);
  559.                 $additionalServicesList->setParameter($params);
  560.                 $products += $additionalServicesList->getItems(0100);
  561.             }
  562.             $viewParams["startdate"] = $reqProd[$prodid]['date'];
  563.             $viewParams["products"] = $products;
  564.             $viewParams["from"] = $dateFrom;
  565.             $viewParams["to"] = $dateTo;
  566.             $viewParams["service"] = $additionalService;
  567.             if ($request->headers->get('format') === 'json') {
  568.                 $response = [
  569.                     'success' => true,
  570.                     'content' => $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/productsAjax.html.twig'$viewParams)->getContent()
  571.                 ];
  572.                 return $this->json($response);
  573.             }
  574.         }
  575.         return null;
  576.     }
  577.     public function addAdditionalItemAction(Request $request): Response
  578.     {
  579.         $items = [];
  580.         Factory::getInstance()->getEnvironment()->removeCustomItem('demi_cartName');
  581.         $productId $request->get('offerId');
  582.         $product AbstractObject::getById($productId);
  583.         $count $request->get('count');
  584.         $cart $this->ecommerceHelper->getCart();
  585.         if ($product instanceof AdditionalProduct) {
  586.             $cartItemInfo $this->cartItemInfoService->fillAdditionalServiceProductFromRequest(new CartItemInfoHolder(), $request);
  587.             $cartItemInfo->setAdditionalInfo(['relatedProduct' => $request->get('relatedProduct'), 'ignoreInCartTemplate' => true]);
  588.             $cancelationService = new CancellationInformation();
  589.             $cancelationInfo $cancelationService->getCancellationInformation($product->getParent()->getParent(),
  590.                 $product,
  591.                 $product->getParent()->getParent()->getDbCode(),
  592.                 Config::getInstance()->getSalesChannel(),
  593.                 $cartItemInfo->getDateFrom(),
  594.                 $cartItemInfo->getDateTo(),
  595.                 $cartItemInfo->getAdultAmount(),
  596.                 $cartItemInfo->getChildrenAges(),
  597.                 $cartItemInfo->getDurationValue());
  598.             $cartItemInfo->setCancellationInformation($cancelationInfo);
  599.             $this->ecommerceHelper->addProductToCart($count$product$cartItemInfo$cart);
  600.             $this->trackingManager->trackCartProductActionAdd($cart$product$count);
  601.             $items[] = ['persons' => $count'product' => $product'price' => $this->paymentHelper->getPriceObject($cartItemInfo->getPrice()), 'count' => $count];
  602.         } else {
  603.             foreach ($request->get('products') as $productId => $productData) {
  604.                 $selectedDay $productData['days'];
  605.                 $selectedDay explode(';'$selectedDay);
  606.                 $price $selectedDay[2];
  607.                 $duration $selectedDay[0];
  608.                 $request->request->set('additionalInformation'$productData);
  609.                 if (!$productData['count'] || !$productData['date'] || !$price) {
  610.                     throw new InvalidArgumentException('some parameters are missing. cannot add product to cart');
  611.                 }
  612.                 $request->request->set('count'$productData['count']);
  613.                 $request->request->set('offerId'$productId);
  614.                 $request->request->set('date'$productData['date']);
  615.                 $request->request->set('price'$price);
  616.                 $request->request->set('totalPrice'$price);
  617.                 $request->request->set('settlerCode'$productData['settlerCode']);
  618.                 $request->request->set('durationType'$productData['durationType']);
  619.                 $request->request->set('durationValue'$duration);
  620.                 $cartItemInfo $this->cartItemInfoService->fillAdditionalServiceProductFromRequest(new CartItemInfoHolder(), $request);
  621.                 $cartItemInfo->setAdditionalInfo(['relatedProduct' => $request->get('relatedProduct')]);
  622.                 $additionalInfo $productData;
  623.                 unset($additionalInfo['date'], $additionalInfo['count'], $additionalInfo['settlerCode'],
  624.                     $additionalInfo['price'], $additionalInfo['totalPrice'], $additionalInfo['durationValue'],
  625.                     $additionalInfo['durationType'], $additionalInfo['days']);
  626.                 $count $request->get('count');
  627.                 if (!empty($additionalInfo)) {
  628.                     //process if any arrays be here
  629.                     foreach ($additionalInfo as $key => $info) {
  630.                         foreach ($info as $k => $v) {
  631.                             if (is_array($v)) {
  632.                                 $additionalInfo[$key][$k] = implode('<br>'$v);
  633.                             }
  634.                         }
  635.                     }
  636.                     $cartItemInfo->setAdditionalInfo($additionalInfo);
  637.                     $persons max(array_keys($additionalInfo));
  638.                     if ($persons <= 0) {
  639.                         $persons $count;
  640.                     }
  641.                     $cartItemInfo->setAdultAmount($persons);
  642.                 }
  643.                 $cancelationService = new CancellationInformation();
  644.                 $cancelationInfo $cancelationService->getCancellationInformation($product->getParent()->getParent(),
  645.                     $product,
  646.                     $product->getParent()->getParent()->getDbCode(),
  647.                     Config::getInstance()->getSalesChannel(),
  648.                     $cartItemInfo->getDateFrom(),
  649.                     $cartItemInfo->getDateTo(),
  650.                     $cartItemInfo->getAdultAmount(),
  651.                     $cartItemInfo->getChildrenAges(),
  652.                     $cartItemInfo->getDurationValue());
  653.                 $cartItemInfo->setCancellationInformation($cancelationInfo);
  654.                 $this->ecommerceHelper->addProductToCart($count$product$cartItemInfo$cart);
  655.                 $items[] = ['persons' => $persons'product' => $product'price' => $this->paymentHelper->getPriceObject($cartItemInfo->getPrice()), 'count' => $count];
  656.             }
  657.         }
  658.         $response = [
  659.             'status' => 'success',
  660.             'html' => $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/package-add-cart.html.twig', [
  661.                 'totalCount' => $cart->getItemAmount(),
  662.                 'cartItems' => $items
  663.             ])->getContent()
  664.         ];
  665.         return $this->json($response);
  666.     }
  667.     public function productDetailAction(Request $request): Response
  668.     {
  669.         $productId $request->get('id');
  670.         $price $request->get('price');
  671.         $viewParams["showPrice"] = true;
  672.         $product AdditionalProduct::getById($productId);
  673.         $additionalService $product->getParent();
  674.         if ($additionalService && $additionalService->getParent() && $additionalService->getParent() instanceof AdditionalServiceProvider) {
  675.             Config::setClientKeyOverrideFromObject($additionalService->getParent());
  676.         }
  677.         if($request->get('date')){
  678.             $dateFrom $this->elementsCustomDateFormat->stringToCarbon($request->get('date'));
  679.             $dateTo = clone  $dateFrom;
  680.         } else {
  681.             //legacy?
  682.             $dateFrom $request->get('from') ? $this->elementsCustomDateFormat->stringToCarbon($request->get('from')) : new Carbon();
  683.             $dateTo $request->get('to') ? $this->elementsCustomDateFormat->stringToCarbon($request->get('to')) : (new Carbon())->addMonth();
  684.         }
  685.         $period $dateFrom->diffInDays($dateTo) + 1;
  686.         $paramLines = new Line();
  687.         $paramLines->setDateFrom($dateFrom);
  688.         $paramLines->setDateTo($dateTo);
  689.         $paramLines->setPeriod($period);
  690.         $paramLines->setProductId($product->getId());
  691.         $paramLines->setBookOnly(false);
  692.         $params = new Parameter();
  693.         $params->setBookOnly(false);
  694.         $params->setSearchLines([$paramLines]);
  695.         $params->setPage(1);
  696.         $params->setPerPage(100);
  697.         $this->additionalServiceListingLocal->setParameter($params);
  698.         $this->additionalServiceListingLocal->load();
  699.         $paramLines->setUnits(1);
  700.         $params->setSearchLines([$paramLines]);
  701.         $this->additionalServiceListingLive->setParameter($params);
  702.         $this->additionalServiceListingLive->load();
  703.         $result $this->additionalServiceListingLive->getItems(1100);
  704.         $bookable false;
  705.         if($result) {
  706.             foreach ($result as $item) {
  707.                 $prices $item->getPrices();
  708.                 if (reset($prices)->getBookable()) {
  709.                     $bookable true;
  710.                     break;
  711.                 }
  712.             }
  713.         }
  714.         $viewParams["id"] = $productId;
  715.         $viewParams["price"] = $price;
  716.         $viewParams["arrivalDate"] = $dateFrom;
  717.         $viewParams["departureDate"] = $dateTo;
  718.         foreach ($product->getAdditionalService()->getDescriptions() as $description) {
  719.             $descriptionTypes[] = $description->getDescriptionType();
  720.             if ($description->getDescriptionType() !== DescriptionInterface::DESCRIPTION_SERVICE_DESCRIPTION) {
  721.                 $tosc5texts[$description->getDescriptionType()] = $description;
  722.             }
  723.         }
  724.         $viewParams["productText"] = $product->getDescription();
  725.         $viewParams["tosc5texts"] = $tosc5texts ?? [];
  726.         $viewParams["text"] = $product->getAdditionalService()->getDescription();
  727.         $viewParams["title"] = $product->getName();
  728.         $viewParams["additionalServiceItem"] = $this->additionalServiceListingLive;
  729.         $viewParams["additionalServiceItems"] = $this->additionalServiceListingLocal;
  730.         $viewParams["isPackage"] = !empty($request->get('packages'));
  731.         $viewParams["searchParam"] = $params;
  732.         $viewParams["maxSelect"] = 20;
  733.         if(!$bookable){
  734.             return $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/includes/offerDetailEnquiry.desktop.html.twig'$viewParams);
  735.         } else {
  736.             return $this->renderTemplate('@ElementsDemiFrontend/AdditionalService/includes/offerDetailContent.desktop.html.twig'$viewParams);
  737.         }
  738.     }
  739.     protected function findEditablePrefix(Document $document): string
  740.     {
  741.         if ($document->getEditables() !== null) {
  742.             $areaBlock current(array_filter($document->getEditables(), static function ($editable) {
  743.                 return $editable instanceof Areablock;
  744.             }));
  745.             if ($areaBlock !== false && !empty($areaBlock->getIndices())) {
  746.                 $correctIndex array_filter($areaBlock->getIndices(), static function ($index) {
  747.                     return $index['type'] === 'demi-additionalservice-list';
  748.                 });
  749.                 return $areaBlock->getName() . ':' current($correctIndex)['key'] . '.';
  750.             }
  751.         }
  752.         // Fallback for overriden documents
  753.         if ($document->getContentMasterDocument() !== null) {
  754.             return $this->findEditablePrefix($document->getContentMasterDocument());
  755.         }
  756.         return '';
  757.     }
  758.     private function sendEnquire(Request $request\Elements\Demi\Model\AdditionalService $additionalServiceUserInterface $user null): bool
  759.     {
  760.         if ($this->configuration->getDisableEnquiry()) {
  761.             return false// Just return since detailEnquireAction is called from detailAction
  762.         }
  763.         // date
  764.         $fromDate $this->elementsCustomDateFormat->stringToCarbon($request->get('date'));
  765.         $request->query->set('from'ElementsCustomDateFormat::toString($fromDate));
  766.         $request->query->set('to'ElementsCustomDateFormat::toString($fromDate));
  767.         if (!empty($this->configuration->getPrivacyPolicyEnquiry())) {
  768.             $ppAccepted = (bool)$request->get('pp');
  769.         } else {
  770.             $ppAccepted true;
  771.         }
  772.         $request->query->set('accoIds', [$additionalService->getParent()->getId()]);
  773.         $request->query->set('pp'$ppAccepted);
  774.         $request->query->set('price'null);
  775.         $request->query->set('u0'$request->get('count') ?? );
  776.         $productIds $request->get('productId');
  777.         $time $request->get('time') ? $this->elementsCustomDateFormat->stringToCarbon($request->get('time')) : '';
  778.         if($time instanceof Carbon){
  779.             $time $time->format('H:i');
  780.         }
  781.         $products=[['id'=>$productIds'units' =>$request->get('count'), 'time' => $time]];
  782.         $request->query->set('comment'$this->getCommentForDSIEnquiry($request$products));
  783.         if($additionalService->getParent()->getTown()){
  784.             $request->query->set('towns', [$additionalService->getParent()->getTown()->getId()]);
  785.         }
  786.         $mailSent $this->savingRequestController->serviceProviderRequestAction($request$user);
  787.         return $mailSent;
  788.     }
  789.     /**
  790.      * @param Request $request
  791.      * @param $package
  792.      * @param $products
  793.      * @param $nights
  794.      *
  795.      * @return mixed|string
  796.      */
  797.     private function getCommentForDSIEnquiry(Request $request,  array $products): mixed
  798.     {
  799.         $comment $request->get('comment');
  800.         foreach ($products as $productInfo) {
  801.             $product \Elements\Demi\AbstractObject::getById($productInfo['id']);
  802.             $units $productInfo['units'];
  803.             $time = isset($productInfo['time']) ? $productInfo['time'] : '';
  804.             if ($product) {
  805.                 $prodStr '';
  806.                 if ($units) {
  807.                     $prodStr .= $units 'x ';
  808.                 }
  809.                 $prodStr .= $product->getName();
  810.                 $comment .= " "$prodStr;
  811.                 if(!empty($time)){
  812.                     $comment .= " ".$time;
  813.                 }
  814.             }
  815.         }
  816.         if ($request->get('tel')) {
  817.             $comment .= ', Tel.: '.$request->get('tel');
  818.         }
  819.         return $comment;
  820.     }
  821. }