<?php
/**
* Elements DeMI
*
* This source file is available under the elements DeMI license version 1
*
* @copyright Copyright (c) elements.at New Media Solutions GmbH (https://www.elements.at/)
* @license elements DeMI Lizenz Version 1 (https://www.elements.at/de/demi-lizenz)
*/
namespace Elements\Bundle\DemiFrontendBundle\Controller;
use Carbon\Carbon;
use Elements\Bundle\CmsToolsBundle\Tool\Helper\ElementsCustomDateFormat;
use Elements\Bundle\DemiFrontendBundle\Service\AccommodationResultSet;
use Elements\Bundle\DemiFrontendBundle\Service\BubbleCalculator;
use Elements\Bundle\DemiFrontendBundle\Service\BubbleCalculatorCorridor;
use Elements\Bundle\DemiFrontendBundle\Service\BubbleCalculatorHousePackages;
use Elements\Demi\Frontend\Service\Configuration;
use Elements\Bundle\DemiFrontendBundle\Service\DemiStateHelper;
use Elements\Bundle\DemiFrontendBundle\Service\DemiUrl;
use Elements\Bundle\DemiFrontendBundle\Service\PaginatorHelper;
use Elements\Bundle\DemiFrontendBundle\Service\SearchFrontend;
use Elements\Bundle\DemiFrontendBundle\Service\SortFactory;
use Elements\Bundle\DemiFrontendBundle\Service\SortFactoryNearbySearch;
use Elements\Bundle\SeoHelperBundle\Templating\Helper\Robots;
use Elements\Demi\Accommodation\Search\Listing;
use Elements\Demi\Accommodation\Search\Listing\VacancyLocal;
use Elements\Demi\Accommodation\Search\Parameter;
use Elements\Demi\Accommodation\Search\ResultSet;
use Elements\Demi\Deskline\Accommodation\Search\Service\Vacancies\Live;
use Elements\Demi\Model\AccommodationServiceProvider;
use Elements\Demi\Model\Category;
use Elements\Demi\Model\Classification;
use Elements\Demi\Model\Facility;
use Elements\Demi\Model\HolidayTheme;
use Elements\Demi\Model\HousePackageMaster;
use Elements\Demi\Model\MarketingGroup;
use Elements\Demi\Model\Region;
use Elements\Demi\Model\Stars;
use Elements\Demi\Model\Town;
use ErrorException;
use Exception;
use Jaybizzle\CrawlerDetect\CrawlerDetect;
use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
use Pimcore\Model\DataObject\DemiFilterObject;
use Pimcore\Model\DataObject\DemiStars;
use Pimcore\Model\Document;
use Pimcore\Model\Document\Snippet;
use Pimcore\Tool\DeviceDetector;
use Pimcore\Translation\Translator;
use Pimcore\Twig\Extension\Templating\PimcoreUrl;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ListController extends AbstractDemiController
{
protected array $enabledFeatures = [];
public function __construct(Configuration $demiConfigurationHelper,
protected SearchFrontend $searchParams,
protected VacancyLocal $vacancyLocal,
protected BubbleCalculator $bubbleCalculator,
protected BubbleCalculatorCorridor $bubbleCalculatorCorridor,
protected BubbleCalculatorHousePackages $bubbleCalculatorHousePackages,
protected Robots $robots,
protected VacancyLocal $corridor,
protected ?SortFactoryNearbySearch $sortFactoryNearbySearch,
protected SortFactory $sortFactoryHelper,
protected ?Live $desklineLive,
protected DemiUrl $demiUrl,
protected Translator $translator,
protected AccommodationResultSet $demiAccommodationResultSetHelper,
protected DemiStateHelper $demiStateHelper,
protected PimcoreUrl $pimcoreUrl,
protected PaginatorHelper $paginatorHelper,
protected ParameterBagInterface $parameterBag,
ElementsCustomDateFormat $elementsCustomDateFormat
) {
$this->setConfiguration($demiConfigurationHelper);
$this->setElementsCustomDateFormat($elementsCustomDateFormat);
$this->enabledFeatures = $this->parameterBag->has("elements_demi.enabled_features") ? $this->parameterBag->get("elements_demi.enabled_features") : [];
}
public function listAction(Request $request): JsonResponse|Response
{
$crawlerDetect = new CrawlerDetect();
if($crawlerDetect->isCrawler()){
$params = $request->query->all();
foreach(array_keys($params) as $paramKey){
if(!in_array($paramKey,['randSeed', 'page', 'asSitemapXml'])){
//TODO logrotate / cleanup?
file_put_contents(DEMI_LOG_PATH . '/blocked_crawler_request.log', "[" . Carbon::now()->format("Y-m-d H:i:s") . "] " . $crawlerDetect->getMatches(). ": " . $request->getRequestUri() .PHP_EOL, FILE_APPEND | LOCK_EX);
//bye, bye crawler
$this->redirect($this->configuration->getAccoParentDocument(), 303);
}
}
}
$viewParams["accommodationListTilesEnabled"] = $this->enabledFeatures['accommodationListTiles'];
$deviceDetector = DeviceDetector::getInstance();
$device = $deviceDetector->isPhone() ? 'mobile' : 'desktop';
if (!$this->editmode) {
if (empty($this->configuration->getCurrentDocument())) {
$this->configuration->setDocument($this->document ?? $request->attributes->get('contentDocument'));
}
$isNoDate = $request->get('from') === null && $request->get('to') === null;
$viewParams["noDate"] = $isNoDate;
if ($redirect = $this->needsTimeStringRedirect($request)) {
return $this->redirect($redirect);
}
$searchError = null;
$useAlternativeSearch = false;
$searchParams = $this->searchParams->getSearchParam(null, false, true);
if (!$isNoDate && empty($searchParams->getRoomrows())) {
//faulty search parameters
$searchError = true;
}
$housePackageContainer = $this->document->getProperty('demi_housePackageContainer');
$housePackageMasterSelfAssign = $this->document->getProperty('demi_housePackageMasterSelfAssign');
if (!$this->editmode && $housePackageContainer !== null) {
if (!$housePackageContainer->isPublished()) {
throw new NotFoundHttpException('List uses unpublished HousePackageContainer');
}
if (!empty($housePackageContainer->getPackageDuration())) {
$viewParams["tvbPackage"] = $housePackageContainer;
}
} elseif (!$this->editmode && $housePackageMasterSelfAssign !== null) {
if (!$housePackageMasterSelfAssign->isPublished()) {
throw new NotFoundHttpException('List uses unpublished HousePackageMasterSelfAssign');
}
}
$itemCountPerPage = 10;
if (!$deviceDetector->isPhone()) {
$itemCountPerPage = $this->configuration->getItemCountPerPageDesktop();
}
// for list-search-params we set the currentItemCountPerPage and page since this needs to be passed in case of a life request
$searchParams->setCurrentItemCountPerPage($itemCountPerPage);
$searchParams->setPage($request->get('page', 1));
$accoParentFullPath = $this->configuration->getAccoParentDocument()->getFullPath();
if (str_starts_with($accoParentFullPath, 'http://') || str_starts_with($accoParentFullPath, 'https://')) {
//acco parent document seems to be from a different site
if (!str_starts_with($accoParentFullPath, $request->getSchemeAndHttpHost())) {
//just to make sure that domains really differ
throw new ErrorException('Please check config Object in use, it does not match the current domain. Acco parent document location: ' . $this->configuration->getAccoParentDocument()->getFullPath() . ' vs. current request scheme and host: ' . $request->getSchemeAndHttpHost());
}
}
if ($searchParams->getIsCorridor()) {
$listing = $this->corridor;
} else {
$listing = $this->vacancyLocal;
}
$listing->setSortFactories(array_merge($this->sortFactoryHelper->getAdditionalSortFactories(),
[$this->sortFactoryNearbySearch]));
if ($searchParams->getSearchType() == Parameter::SEARCH_TYPE_ONE_PRODUCT_PER_PACKAGE) {
$listing->setReturnType(Listing::RETURNTYPE_PACKAGES);
$viewParams["isPackageSearch"] = true;
} else {
$viewParams["isPackageSearch"] = false;
}
$viewParams["language"] = $request->getLocale();
$searchParams->setSkipCleaningSortAfterFactory((bool)$request->get('skipCleaningSortAfterFactory'));
$listing->setParameter($searchParams);
if ($request->get('isAlternative')) {
$itemCountPerPage -= $searchParams->getPreviousResults();
}
$paginator = $this->paginatorHelper->getPaginator($listing, $itemCountPerPage, $request->get('page', 1)); // new Paginator($listing);
$viewParams["paginator"] = $paginator;
if ($searchError || $paginator->getTotalItemCount() == 0) {
$error = $this->searchParams->getSearchError($searchParams);
$searchError = $error;
}
$isEnabledAlternativeSearch = $this->configuration->getEnableAlternativeSearch();
$viewParams["enabledAlternativeSearch"] = $isEnabledAlternativeSearch;
$minResult = $this->configuration->getMinAccoCntFiltering();
$viewParams["minResult"] = $minResult;
if ($isEnabledAlternativeSearch && $searchParams->getDateFrom() && $searchParams->getDateTo() &&
(!$searchParams->getNights() || ($searchParams->getNights() == '')) && ($searchError == null)) {
$useAlternativeSearch = true;
}
if ($request->get('isAlternative')) {
if ($paginator->getTotalItemCount() == 0) {
$viewParams["showAlternativeResults"] = false;
} else {
$viewParams["showAlternativeResults"] = true;
$request->query->remove('ajax');
$request->query->remove('excludeAccoIds');
$request->query->remove('isAlternative');
$request->query->remove('previousResults');
$queryString = '?' . $this->getQueryStringFromParams($request->query->all());
$viewParams["reloadListUrl"] = $queryString;
}
} elseif ($useAlternativeSearch && $paginator->getTotalItemCount() <= $minResult) {
$viewParams["makeAlternativeSearchRequest"] = true;
$excludeAccoIds = $listing->getResultSet()->getAccoIds();
// TODO use excludeProductIds for package list
$alterantiveUrl = $this->getAlternativeSearchUrl($searchParams, $request, $excludeAccoIds);
$viewParams["accoListAlternativeUrl"] = $alterantiveUrl;
} else {
$viewParams["makeAlternativeSearchRequest"] = false;
}
$viewParams["availabilityIsChecked"] = !$searchParams->isNoDate();
$viewParams["searchParam"] = $searchParams;
$viewParams["error"] = $request->get('error');
if ($searchError) {
$viewParams["searchError"] = $searchError;
}
$priceRange = $listing->getPriceRange();
// if minPrice and maxPrice equal - set min to 0
if ($priceRange[0] == $priceRange[1]) {
$priceRange[0] = 0;
}
if ($this->configuration->getContentScoreFilterAccommodation() > 0) {
$searchParams->setContentScore($this->configuration->getContentScoreFilterAccommodation());
}
$viewParams["minPrice"] = $priceRange[0];
$viewParams["maxPrice"] = $priceRange[1];
$viewParams["freeCancellationInList"] = $this->enabledFeatures['freeCancellationInList'];
if ($request->isXmlHttpRequest()) {
$this->addResponseHeader('X-Robots-Tag', 'noindex,nofollow');
} else {
$this->setRobots($request);
}
if ($request->get('asSitemapXml')) {
if (!$searchParams->isNoDate()) {
throw new Exception('sitemap cannot have date params!');
}
$this->addResponseHeader('Content-Type', 'text/xml');
$sitemapdata = $this->generateSitemapData($paginator, $searchParams, $request->getLocale());
return $this->renderTemplate('@ElementsDemiFrontend/List/sitemap.html.twig', ['sitemapdata' => $sitemapdata]);
}
if ($request->get('asJsonSumary') && $request->isXmlHttpRequest()) {
/**
* @var ResultSet $resultSet
*/
$resultSet = $listing->getResultSet();
$data = [
'totalItems' => $resultSet->getCount(),
'type' => $listing->getReturnType(),
'minPrice' => $resultSet->getMinPriceFromBase()
];
return $this->json($data);
}
if ($request->get('asJsonList')) {
$this->addResponseHeader('Content-Type', 'application/json');
$this->robots->setNoIndex();
$this->robots->setRobots();
$jsonData = $this->generateJsonData($listing, $searchParams->isNoDate(), $request, $housePackageContainer, $viewParams["isPackageSearch"]);
return $this->renderTemplate('@ElementsDemiFrontend/List/json.html.twig', ['jsonData' => $jsonData]);
}
if ($request->get('asJsonTypeAhead')) {
$this->addResponseHeader('Content-Type', 'application/json');
$this->robots->setNoIndex();
$this->robots->setRobots();
return $this->renderTemplate('@ElementsDemiFrontend/List/jsonTypeahead.html.twig', ['localListing' => $paginator, 'noDate' => $searchParams->isNoDate(), 'page' => $request->get('page')]);
}
if ($request->isXmlHttpRequest()) {
if ($searchParams->getIsAlternative()) {
return $this->renderTemplate('@ElementsDemiFrontend/List/includes/acco-list-alternative-results.html.twig', array_merge($viewParams, ['accommodationPaginator' => $paginator]));
}
return $this->renderTemplate('@ElementsDemiFrontend/List/list-content.html.twig', array_merge($viewParams, ['accommodationPaginator' => $paginator]));
}
}
return $this->render('@ElementsDemiFrontend/List/list.' . $device . '.html.twig', $viewParams);
}
public function getAlternativeSearchUrl(Parameter $params, Request $request, array $excludeAccoIds): string
{
$alternativeParams = $this->getAlternativeSearchParams($params);
$newFrom = $alternativeParams->getDateFrom();
$newTo = $alternativeParams->getDateTo();
$nights = $alternativeParams->getNights();
$startNight = $nights[0];
$endNight = $nights[count($nights) - 1];
$alternativeSearchIgnoreFilterFields = explode(', ', $this->configuration->getAlternativeSearchIgnoreFilterFields());
$queryString = '?ajax=1&isAlternative=1&';
$existingParams = $request->query->all();
$newParams = [];
foreach ($existingParams as $key => $value) {
$newParams[$key] = $this->cleanRequestParam($value);
}
$newParams['durationType'] = 'variable';
$newParams['durationNights'] = $startNight . '-' . $endNight;
$newParams['from'] = $this->elementsCustomDateFormat->dateToString($newFrom);
$newParams['to'] = $this->elementsCustomDateFormat->dateToString($newTo);
$newParams['priceFrom'] = '';
$newParams['priceTo'] = '';
$newParams['excludeAccoIds'] = $excludeAccoIds;
$newParams['previousResults'] = count($excludeAccoIds);
foreach ($alternativeSearchIgnoreFilterFields as $key) {
if (in_array($key, $alternativeSearchIgnoreFilterFields)) {
$newParams[$key] = '';
}
}
$queryString .= $this->getQueryStringFromParams($newParams);
return $queryString;
}
private function cleanRequestParam(mixed $value): array|string
{
if (!is_array($value)) {
return htmlentities(strip_tags($value));
}
$cleaned = [];
foreach ($value as $k => $v) {
$cleaned[$k] = $this->cleanRequestParam($v);
}
return $cleaned;
}
private function getQueryStringFromParams(array $params) : string
{
$queryString = '';
foreach ($params as $key => $val) {
if (!empty($val)) {
if (is_string($val) || is_int($val)) {
$queryString .= $key . '=' . $val . '&';
} elseif (is_array($val)) {
foreach ($val as $item) {
$queryString .= $key . '[]=' . $item . '&';
}
}
}
}
return substr($queryString, 0, -1);
}
public function getBubblesAction(Request $request): JsonResponse
{
if (empty($this->configuration->getCurrentDocument())) {
$this->configuration->setDocument($this->document ?? $request->attributes->get('contentDocument'));
}
if (!$this->document->getEditable('filterSnippet')) {
throw new NotFoundHttpException('could calculate bubbles, because could not find filter snippet');
}
$filterSnippet = $this->document->getEditable('filterSnippet')->getData();
$enabledFilters = $this->configuration->getFilterBlockItems();
$noBubbles = ['showAcconame', 'autoComplete', 'bestPriceType'];
$requiredBubbles = $requiredParams = $allArray = [];
$bestPriceType = null;
if(is_array($filterSnippet) && $filterSnippet['id']){
$filterSnippet = Document::getById($filterSnippet['id']);
}
$elements = [];
if ($filterSnippet instanceof Snippet) {
$elements = $filterSnippet->getEditables();
if ($filterSnippet->getContentMasterDocument() instanceof Snippet) {
$elements = array_replace($filterSnippet->getContentMasterDocument()->getEditables(), $elements);
}
}
$allArrayGroupedByElement = [];
foreach ($elements as $element) {
$name = null;
foreach ($enabledFilters as $key => $value) {
if (strrpos($element->getName(), $key) !== false) {
$name = $key;
}
}
if ($name === 'bestPriceType') {
$bestPriceType = $element->getValue();
}
if ($name && array_key_exists($name, $enabledFilters) && $enabledFilters[$name]['enabled'] && !in_array($name, $noBubbles)) {
$requiredBubbles[$element->getName()] = $name;
$requiredParams[$name][] = $enabledFilters[$name]['value'];
if (method_exists($element, 'getElements')) {
foreach ($element->getElements() as $el) {
if ($el) {
$allArray[$name][] = $el->getO_id();
$allArrayGroupedByElement[$element->getName()][$name][] = $el->getO_id();
}
}
}
}
}
$bubbles = [];
if ($bestPriceType && $requiredParams['showBestpriceOnly'] && $requiredParams['showBestpriceOnly'][0] && strtolower($requiredParams['showBestpriceOnly'][0]) != strtolower($bestPriceType)) {
$requiredParams['showBestpriceOnly'][0] = $bestPriceType;
}
if (is_array($requiredBubbles)) {
$bubbleCats = $this->setupBubbles($requiredBubbles, $allArray, $allArrayGroupedByElement, $requiredParams, $request);
foreach ($bubbleCats as $bubbleCat) {
foreach ($bubbleCat as $key => $bubbleCount) {
$bubbles[$key] = $bubbleCount;
}
}
}
return $this->json($bubbles);
}
protected function setupBubbles(array $requiredBubbles, array $allArray, array $allArrayGroupedByElement, array $requiredParams, Request $request): array
{
if ($request->get('noBubbles')) {
return [];
}
$params = $this->searchParams->getSearchParam(null, false, true);
if ($params->getIsCorridor()) {
$bubbleHelper = $this->bubbleCalculatorCorridor;
} elseif ($params->getSearchType() == Parameter::SEARCH_TYPE_ONE_PRODUCT_PER_PACKAGE) {
$bubbleHelper = $this->bubbleCalculatorHousePackages;
} else {
$bubbleHelper = $this->bubbleCalculator;
}
$bubbleHelper->setSearchParameter($params);
$bubbles = [];
foreach ($requiredBubbles as $key => $bubble) {
$allRequiredParams = $requiredParams[$bubble];
if (in_array('fo', $allRequiredParams, true)) {
// find out if mixed element
$allRequiredInElement = [];
foreach ($allArrayGroupedByElement[$key][$bubble] as $foId) {
$filterObject = DemiFilterObject::getById($foId);
$allRequiredParams = $requiredParams[$bubble];
if ($filterObject) {
foreach ($filterObject->getFilterElements() as $item) {
// allowed types for filterObjects
if ($item instanceof Town) {
$allRequiredParams[] = 'towns';
} elseif ($item instanceof Region) {
$allRequiredParams[] = 'regions';
} elseif ($item instanceof MarketingGroup) {
$allRequiredParams[] = 'marketingGroups';
} elseif ($item instanceof Facility) {
$allRequiredParams[] = 'facilities';
} elseif ($item instanceof Stars) {
$allRequiredParams[] = 'stars';
} elseif ($item instanceof Classification) {
$allRequiredParams[] = 'classifications';
} elseif ($item instanceof Category) {
$allRequiredParams[] = 'categories';
} elseif ($item instanceof HolidayTheme) {
$allRequiredParams[] = 'holidayThemes';
}
}
// filterObjects are not allowed to have different types of children, except regions mixed with towns or stars mixed with classifications
$allRequiredInElement = [...$allRequiredParams, ...$allRequiredInElement];
}
}
//end
$allRequiredInElement = array_unique($allRequiredInElement);
foreach ($allArrayGroupedByElement[$key][$bubble] as $foId) {
$filterObject = DemiFilterObject::getById($foId);
$allRequiredParams = $requiredParams[$bubble];
if ($filterObject) {
$items = [];
foreach ($filterObject->getFilterElements() as $item) {
$items[] = $item->getId();
// allowed types for filterObjects
if ($item instanceof Town) {
$allRequiredParams[] = 'towns';
} elseif ($item instanceof Region) {
$allRequiredParams[] = 'regions';
} elseif ($item instanceof MarketingGroup) {
$allRequiredParams[] = 'marketingGroups';
} elseif ($item instanceof Facility) {
$allRequiredParams[] = 'facilities';
} elseif ($item instanceof Stars) {
$allRequiredParams[] = 'stars';
} elseif ($item instanceof Classification) {
$allRequiredParams[] = 'classifications';
} elseif ($item instanceof Category) {
$allRequiredParams[] = 'categories';
} elseif ($item instanceof HolidayTheme) {
$allRequiredParams[] = 'holidayThemes';
}
}
// filterObjects are not allowed to have different types of children, except regions mixed with towns or stars mixed with classifications
$allRequiredParams = array_unique($allRequiredParams);
if (count($allRequiredParams) == 2 || ((count($allRequiredParams) == 3 &&
(in_array('towns', $allRequiredParams) && in_array('regions', $allRequiredParams))) ||
(in_array('stars', $allRequiredParams) && in_array('classifications', $allRequiredParams)))) {
if ($allRequiredInElement[0] === 'fo' && count($allRequiredInElement) > 2) {
//filter Elements with filter objects containing different types
$allRequiredParams = $allRequiredInElement;
}
$bubbles[$bubble . 'BubbleArray' . $foId] = $bubbleHelper->getCountForDestIds($items, $allRequiredParams,
stripos($bubble, 'facilities') !== false, (int)($foId));
}
}
}
} else {
if(!array_key_exists($bubble,$allArray)){
$allArray[$bubble] = [];
}
$bubbles[$bubble . 'BubbleArray'] = $bubbleHelper->getCountForDestIds($allArray[$bubble], $allRequiredParams,
stripos($bubble, 'facilities') !== false);
}
}
return $bubbles;
}
protected function setRobots(Request $request): void
{
//set robots in meta tags
$robotsParams = $request->query->all();
if (count($robotsParams)) {
$this->robots->setNoIndex();
$this->robots->setRobots();
}
}
private function generateSitemapData($paginator, $searchParam, string $language): array
{
$detailUrls = [];
$isPackageSearch = $searchParam->getSearchType() == Parameter::SEARCH_TYPE_ONE_PRODUCT_PER_PACKAGE;
if ($isPackageSearch) {
foreach ($paginator as $resultSet) {
$accoObject = AccommodationServiceProvider::getById($resultSet->getAccommodationId());
$housePackageMaster = HousePackageMaster::getById($resultSet->getHousePackageMasterId());
$host = 'https://' . $_SERVER['HTTP_HOST'];
$detailUrl = $this->demiUrl->invoke([
'accommodation' => $accoObject,
'package' => $housePackageMaster
], 'demi_acco_detail_package_page', true, true, $language);
$detailUrls[]['loc'] = $host . $detailUrl;
}
} else {
foreach ($paginator as $resultSet) {
$accoObject = AccommodationServiceProvider::getById($resultSet->getAccommodationId());
$host = 'https://' . $_SERVER['HTTP_HOST'];
$detailUrl = $this->demiUrl->invoke([
'accommodation' => $accoObject
], 'demi_acco_detail_page', true, true, $language);
$detailUrls[]['loc'] = $host . $detailUrl;
}
}
return $detailUrls;
}
private function generateJsonData($localListing, $noDate, Request $request, $tvbPackage, $isPackageSearch): bool|string
{
$results = [];
foreach ($localListing as $entry) {
if ($entry->getAccommodationId()) {
$accommodation = AccommodationServiceProvider::getById($entry->getAccommodationId());
if ($accommodation) {
$classificationsArray = [];
foreach ($accommodation->getClassifications() as $classification) {
$count = $classification->getClassificationGroupAmount();
$group = $classification->getClassificationGroup();
if ($group && $count) {
$classifications = [];
$classifications['name'] = $group->getName();
$classifications['count'] = $count;
if ($group->getIconSmall()) {
$thumbnail = $group->getIconSmall()->getThumbnail('demi-classification');
if ($thumbnail) {
$classifications['html'] = $thumbnail->getHTML();
}
}
$classificationsArray[] = $classifications;
}
}
$badges = null;
foreach ($accommodation->getClassifications() as $classification) {
if (!$classification->getClassificationGroup() && $classification->getName()) {
$badges[] = $classification->getName();
}
}
$stars = $accommodation->getStars();
if ($stars instanceof DemiStars) {
$starNumbers = $stars->getStarsNumbers();
if ($stars->getIsSuperior()) {
$superior = 'S';
}
}
$totalVacancies = $entry->getTotalVacanciesAndOffers()->getTotalVacancies();
$totalOffers = $entry->getTotalVacanciesAndOffers()->getTotalOffers();
if ($totalVacancies < 6) {
$array = (['[AMOUNT_ROOMS]' => $totalVacancies]);
$message = $this->translator->trans('demi.only-x-rooms-free', $array);
$messageSub = $this->translator->trans('demi.list.last-chance');
} else {
$array = (['[AMOUNT_OFFERS]' => $totalOffers]);
$message = $this->translator->trans('demi.x-rooms-free', ['[AMOUNT_ROOMS]' => $totalVacancies]);
$messageSub = $this->translator->trans('demi.choose-from-x-offers', $array);
}
$minPriceMeal = $this->demiAccommodationResultSetHelper->getMinPriceProductMeals($entry);
$facilities = null;
$accoTopFacilities = $this->demiAccommodationResultSetHelper->getTopFacilitiesForAccommodation($accommodation, $this->configuration->getTopFacilities());
foreach ($accoTopFacilities as $topFacility) {
$facilities[] = $topFacility->getName();
}
if ($noDate) {
$fromDate = null;
} else {
$fromDate = new Carbon();
$fromDate->setTimestamp($this->demiStateHelper->getSearchFrom()->getTimestamp());
}
if ($tvbPackage) {
$detailUrl = $this->demiUrl->invoke([
'accommodation' => $accommodation,
'package' => $tvbPackage
], 'demi_acco_detail_tvbpackage_page', $noDate && !$request->get('nearbySearchId'));
} else if ($isPackageSearch) {
$housePackageMaster = HousePackageMaster::getById($accommodation->getHousePackageMasterId());
$detailUrl = $this->demiUrl->invoke([
'accommodation' => $accommodation,
'package' => $housePackageMaster
], 'demi_acco_detail_package_page', $noDate && !$request->get('nearbySearchId'));
} else {
$detailUrl = $this->pimcoreUrl->__invoke([
'object' => $accommodation
], 'demi_acco_detail_page', $noDate && !$request->get('nearbySearchId'));
}
$firstImageUrl = $this->demiUrl->invoke([
'path' => 'demi',
'objectId' => $isPackageSearch ? $housePackageMaster->getId() : $accommodation->getId(),
'fallbackId' => $isPackageSearch ? $accommodation->getId() : '',
'thumbnail' => 'demi-chatbot',
'dateFrom' => $fromDate ? $fromDate->getTimestamp() : ''
], 'demi_image');
$defaultCurrency = Factory::getInstance()->getEnvironment()->getDefaultCurrency();
if ($defaultCurrency) {
$currency = $defaultCurrency->getSymbol();
}
$stars = $accommodation->getStars();
if ($stars instanceof DemiStars) {
$starNumbers = $stars->getStarsNumbers();
if ($stars->getIsSuperior()) {
$superior = 'S';
}
}
if ($accommodation->getRatingSystem() === 'TrustYou' && $accommodation->getRatingCountDirect() > 10) {
$rating = [
'score' => $accommodation->getRatingAverageDirect(),
'text' => $accommodation->getRatingScoreDescription()
];
}
$data = [
'acco_id' => $accommodation->getId(),
'acco_detail_url' => $detailUrl,
'acco_thumb' => $firstImageUrl,
'acco_headline' => $accommodation->getName(),
'acco_stars' => $starNumbers,
'acco_type' => $accommodation->getCategoryNames(),
'acco_town' => $accommodation->getTown() ? $accommodation->getTown()->getName() : '',
'acco_message' => $message,
'acco_message_sub' => $messageSub,
'acco_price_text' => $this->translator->trans('demi.price-from-short'),
'acco_price' => ($entry->getMinPrice() ?: $entry->getMinPriceBase()),
'acco_currency' => $currency,
'acco_meal' => $minPriceMeal,
'acco_facilities' => $facilities,
'acco_classification' => $classificationsArray,
'acco_rating' => $rating,
'acco_superior' => $superior,
'acco_vacancies' => $totalVacancies,
'acco_best_price' => $accommodation->getBestPrice(),
'acco_badges' => $badges
];
$results[] = $data;
}
}
}
return json_encode($results, JSON_THROW_ON_ERROR);
}
}