WIP Refactor filter/search logic

This commit is contained in:
Björn Fromme
2021-08-16 10:58:24 +02:00
parent f2b6f89633
commit d07c07786f
21 changed files with 285 additions and 1168 deletions
@@ -109,12 +109,9 @@ class AjaxDateController extends ActionController
public function datesAction(Product $product, Hotel $hotel = null, $paCode = null, $dateFrom = null, $dateTo = null) public function datesAction(Product $product, Hotel $hotel = null, $paCode = null, $dateFrom = null, $dateTo = null)
{ {
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$filterSettings = $this->filterService->getDefaultFilterSettings( $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids);
$forcedConceptUids, $filterSettings['dateFrom'] = $dateFrom;
false, $filterSettings['dateTo'] = $dateTo;
$dateFrom,
$dateTo
);
$altProductLink = null; $altProductLink = null;
$altLabel = null; $altLabel = null;
@@ -28,7 +28,6 @@ namespace EP\EpProducts\Controller;
***************************************************************/ ***************************************************************/
use EP\EpProducts\Service\FilterService; use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\SearchResultUrlService;
use EP\EpProducts\Service\SearchResultService; use EP\EpProducts\Service\SearchResultService;
use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait;
use EP\EpProducts\Utility\DateUtility; use EP\EpProducts\Utility\DateUtility;
@@ -50,36 +49,29 @@ class AjaxSearchController extends ActionController
*/ */
protected $searchResultService; protected $searchResultService;
/**
* @var SearchResultUrlService
*/
protected $searchResultUrlService;
/** /**
* @param FilterService $filterService * @param FilterService $filterService
* @param SearchResultService $searchResultService * @param SearchResultService $searchResultService
* @param SearchResultUrlService $searchResultUrlService
*/ */
public function __construct public function __construct(FilterService $filterService, SearchResultService $searchResultService)
( {
FilterService $filterService,
SearchResultService $searchResultService,
SearchResultUrlService $searchResultUrlService
) {
parent::__construct(); parent::__construct();
$this->filterService = $filterService; $this->filterService = $filterService;
$this->searchResultService = $searchResultService; $this->searchResultService = $searchResultService;
$this->searchResultUrlService = $searchResultUrlService; }
public function initializeSearchresultAction()
{
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$this->processFilterSettingsArgument($forcedConceptUids);
} }
/** /**
* @param array $filterSettings * @param array $filterSettings
*/ */
public function searchresultAction(array $filterSettings = []) public function searchresultAction(array $filterSettings)
{ {
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$dateSelected = false; $dateSelected = false;
$organic = true; $organic = true;
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
@@ -94,14 +86,12 @@ class AjaxSearchController extends ActionController
->searchResultService ->searchResultService
->getResult($filterSettings, $excludedConceptUids); ->getResult($filterSettings, $excludedConceptUids);
if ($searchResult->getTotal() === 0) { if (0 === $searchResult->getTotal()) {
$filterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids); $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids);
$searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids);
$organic = false; $organic = false;
} }
$this->searchResultUrlService->process($searchResult, $this->settings['defaultDetailPageUid']);
$filterOptions = $this->filterService->getFilterOptions( $filterOptions = $this->filterService->getFilterOptions(
$filterSettings, $filterSettings,
$excludedConceptUids, $excludedConceptUids,
@@ -117,7 +107,7 @@ class AjaxSearchController extends ActionController
'searchresult', 'searchresult',
['filterSettings' => $filterSettings], ['filterSettings' => $filterSettings],
'Search', 'Search',
null, 'epproducts',
'searchresult' 'searchresult'
) )
; ;
@@ -27,8 +27,8 @@ namespace EP\EpProducts\Controller;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\SearchParams;
use EP\EpProducts\Service\FilterService; use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\SearchResultService;
use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
@@ -44,12 +44,19 @@ class AjaxSearchbarController extends ActionController
protected $filterService; protected $filterService;
/** /**
* @param FilterService $filterService * @var SearchResultService
*/ */
public function __construct(FilterService $filterService) protected $searchResultService;
/**
* @param FilterService $filterService
* @param SearchResultService $searchResultService
*/
public function __construct(FilterService $filterService, SearchResultService $searchResultService)
{ {
parent::__construct(); parent::__construct();
$this->filterService = $filterService; $this->filterService = $filterService;
$this->searchResultService = $searchResultService;
} }
public function initializeAction() public function initializeAction()
@@ -58,18 +65,18 @@ class AjaxSearchbarController extends ActionController
} }
/** /**
* @param SearchParams $searchParams * @param array $searchParams
* @return string * @return string
*/ */
public function updateAction(SearchParams $searchParams = null) public function updateAction(array $searchParams = null)
{ {
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
if ($searchParams === null) { if (null === $searchParams) {
$searchParams = new SearchParams(); $searchParams = $this->searchResultService->getNormalizedSearchParams();
$filterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids); $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids);
} else { } else {
$filterSettings = $searchParams->toFilterSettings(); $filterSettings = $this->filterService->getFilterSettingsFromSearchParams($searchParams);
} }
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']); $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']);
$excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']); $excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids']);
@@ -75,12 +75,6 @@ class AjaxTableController extends ActionController
$this->resellerDataService = $resellerDataService; $this->resellerDataService = $resellerDataService;
} }
public function initializeAction()
{
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$this->processFilterSettingsArgument($forcedConceptUids);
}
/** /**
* @param Product $product * @param Product $product
* @param Hotel $hotel * @param Hotel $hotel
@@ -98,11 +92,12 @@ class AjaxTableController extends ActionController
$paCode = null $paCode = null
) { ) {
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$filterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids, false); $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids);
if ($reseller = $this->resellerDataService->getResellerForCurrentDomain()) { if ($reseller = $this->resellerDataService->getResellerForCurrentDomain()) {
$paCode = $reseller['paCode']; $paCode = $reseller['paCode'];
} }
$priceTable = $this->dateService->getPriceTable([ $priceTable = $this->dateService->getPriceTable([
'product' => $product, 'product' => $product,
'hotel' => $hotel, 'hotel' => $hotel,
@@ -138,7 +133,7 @@ class AjaxTableController extends ActionController
$pricetable = $this->dateService->getPriceTable([ $pricetable = $this->dateService->getPriceTable([
'product' => $product, 'product' => $product,
'hotel' => $hotel, 'hotel' => $hotel,
'filterSettings' => $this->filterService->getDefaultFilterSettings(), 'filterSettings' => $this->filterService->getNormalizedFilterSettings(),
'template' => $this->settings['bpnBookingUrlTemplateCode'], 'template' => $this->settings['bpnBookingUrlTemplateCode'],
'paCode' => $paCode, 'paCode' => $paCode,
]); ]);
@@ -30,7 +30,6 @@ namespace EP\EpProducts\Controller;
use EP\EpProducts\Domain\Repository\ProductRepository; use EP\EpProducts\Domain\Repository\ProductRepository;
use EP\EpProducts\Service\FilterService; use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\SearchResultService; use EP\EpProducts\Service\SearchResultService;
use EP\EpProducts\Service\SearchResultUrlService;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
@@ -47,11 +46,6 @@ class AjaxWatchlistController extends ActionController
*/ */
protected $searchResultService; protected $searchResultService;
/**
* @var SearchResultUrlService
*/
protected $searchResultUrlService;
/** /**
* @var FilterService * @var FilterService
*/ */
@@ -60,14 +54,12 @@ class AjaxWatchlistController extends ActionController
/** /**
* @param ProductRepository $productRepository * @param ProductRepository $productRepository
* @param SearchResultService $searchResultService * @param SearchResultService $searchResultService
* @param SearchResultUrlService $searchResultUrlService
* @param FilterService $filterService * @param FilterService $filterService
*/ */
public function __construct public function __construct
( (
ProductRepository $productRepository, ProductRepository $productRepository,
SearchResultService $searchResultService, SearchResultService $searchResultService,
SearchResultUrlService $searchResultUrlService,
FilterService $filterService FilterService $filterService
) )
{ {
@@ -75,7 +67,6 @@ class AjaxWatchlistController extends ActionController
$this->productRepository = $productRepository; $this->productRepository = $productRepository;
$this->searchResultService = $searchResultService; $this->searchResultService = $searchResultService;
$this->searchResultUrlService = $searchResultUrlService;
$this->filterService = $filterService; $this->filterService = $filterService;
} }
@@ -107,11 +98,6 @@ class AjaxWatchlistController extends ActionController
$watchlistData = $this->productRepository->getWatchlist($productUids, $hotelUids); $watchlistData = $this->productRepository->getWatchlist($productUids, $hotelUids);
$watchlist = $this->searchResultService->preprocess($watchlistData); $watchlist = $this->searchResultService->preprocess($watchlistData);
$options = [
'defaultDetailPageUid' => $this->settings['defaultDetailPageUid'],
];
$this->searchResultUrlService->process($watchlist, $options);
$this->view->assignMultiple([ $this->view->assignMultiple([
'watchlist' => $watchlist->getData(), 'watchlist' => $watchlist->getData(),
'total' => $watchlist->getTotal() 'total' => $watchlist->getTotal()
@@ -117,7 +117,7 @@ class ProductController extends ActionController
} }
// Redirect to search page in case no product can be determined // Redirect to search page in case no product can be determined
if ($product === null) { if ($product === null) {
$filterSettings = $this->filterService->getDefaultFilterSettings(); $filterSettings = $this->filterService->getNormalizedFilterSettings();
$this->redirect( $this->redirect(
'searchresult', 'searchresult',
'Search', 'Search',
@@ -202,7 +202,7 @@ class ProductController extends ActionController
$pricetable = $this->dateService->getPriceTable([ $pricetable = $this->dateService->getPriceTable([
'product' => $product, 'product' => $product,
'hotel' => $hotel, 'hotel' => $hotel,
'filterSettings' => $this->filterService->getDefaultFilterSettings(), 'filterSettings' => $this->filterService->getNormalizedFilterSettings(),
]); ]);
$this->view->assign('pricetable', $pricetable); $this->view->assign('pricetable', $pricetable);
} }
@@ -27,16 +27,13 @@ namespace EP\EpProducts\Controller;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\SearchParams;
use EP\EpProducts\Service\FilterService; use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\FilterSettingsEncoder; use EP\EpProducts\Service\FilterSettingsEncoder;
use EP\EpProducts\Service\SearchResultService; use EP\EpProducts\Service\SearchResultService;
use EP\EpProducts\Service\SearchResultUrlService;
use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait;
use EP\EpProducts\Utility\DateUtility; use EP\EpProducts\Utility\DateUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
class SearchController extends ActionController class SearchController extends ActionController
{ {
@@ -53,53 +50,27 @@ class SearchController extends ActionController
*/ */
private $searchResultService; private $searchResultService;
/**
* @var SearchResultUrlService
*/
private $searchResultUrlService;
/**
* @var array
*/
protected $defaultFilterSettings;
/**
* @var \EP\EpProducts\Domain\Model\FilterOptions
*/
protected $defaultFilterOptions;
/** /**
* @param FilterService $filterService * @param FilterService $filterService
*/ */
public function __construct public function __construct(FilterService $filterService, SearchResultService $searchResultService)
( {
FilterService $filterService,
SearchResultService $searchResultService,
SearchResultUrlService $searchResultUrlService
) {
parent::__construct(); parent::__construct();
$this->filterService = $filterService; $this->filterService = $filterService;
$this->searchResultService = $searchResultService; $this->searchResultService = $searchResultService;
$this->searchResultUrlService = $searchResultUrlService;
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true);
$excludedRegionUids = GeneralUtility::trimExplode(',', $this->settings['excludedRegionUids'], true);
$this->defaultFilterSettings = $this->filterService->getDefaultFilterSettings($forcedConceptUids);
$this->defaultFilterOptions = $this->filterService->getFilterOptions(
$this->defaultFilterSettings,
$excludedConceptUids,
$excludedRegionUids,
$forcedConceptUids
);
} }
public function searchbarAction() public function searchbarAction()
{ {
$fixed = (bool)$this->settings['fixed']; $fixed = (bool)$this->settings['fixed'];
$searchParams = new SearchParams($GLOBALS['TSFE']->id); $searchParams = $this->searchResultService->getNormalizedSearchParams();
$filterSettings = $this->filterService->getNormalizedFilterSettings();
$filterOptions = $this->filterService->getFilterOptions($filterSettings);
$this->view->assignMultiple([ $this->view->assignMultiple([
'filterSettings' => $this->defaultFilterSettings, 'filterSettings' => $filterSettings,
'filterOptions' => $this->defaultFilterOptions, 'filterOptions' => $filterOptions,
'searchParams' => $searchParams, 'searchParams' => $searchParams,
'fixed' => $fixed, 'fixed' => $fixed,
]); ]);
@@ -107,10 +78,13 @@ class SearchController extends ActionController
public function searchboxAction() public function searchboxAction()
{ {
$searchParams = new SearchParams($GLOBALS['TSFE']->id); $searchParams = $this->searchResultService->getNormalizedSearchParams();
$filterSettings = $this->filterService->getNormalizedFilterSettings();
$filterOptions = $this->filterService->getFilterOptions($filterSettings);
$this->view->assignMultiple([ $this->view->assignMultiple([
'filterSettings' => $this->defaultFilterSettings, 'filterSettings' => $filterSettings,
'filterOptions' => $this->defaultFilterOptions, 'filterOptions' => $filterOptions,
'searchParams' => $searchParams, 'searchParams' => $searchParams,
]); ]);
} }
@@ -121,11 +95,11 @@ class SearchController extends ActionController
} }
/** /**
* @param SearchParams $searchParams * @param array $searchParams
*/ */
public function processSearchAction(SearchParams $searchParams) public function processSearchAction(array $searchParams)
{ {
$filterSettings = $searchParams->toFilterSettings(); $filterSettings = $this->filterService->getFilterSettingsFromSearchParams($searchParams);
$redirectUri = $this->uriBuilder $redirectUri = $this->uriBuilder
->reset() ->reset()
@@ -144,13 +118,13 @@ class SearchController extends ActionController
public function initializeSearchresultAction() public function initializeSearchresultAction()
{ {
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
//$this->processFilterSettingsArgument($forcedConceptUids); $this->processFilterSettingsArgument($forcedConceptUids);
} }
/** /**
* @param array $filterSettings * @param array $filterSettings
*/ */
public function searchresultAction(array $filterSettings = []) public function searchresultAction(array $filterSettings)
{ {
$dateSelected = false; $dateSelected = false;
$organic = true; $organic = true;
@@ -165,13 +139,11 @@ class SearchController extends ActionController
$searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids);
if ($searchResult->getTotal() === 0) { if ($searchResult->getTotal() === 0) {
$filterSettings = $this->defaultFilterSettings; $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids);
$searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids);
$organic = false; $organic = false;
} }
$this->searchResultUrlService->process($searchResult, $this->settings['defaultDetailPageUid']);
$filterOptions = $this->filterService->getFilterOptions( $filterOptions = $this->filterService->getFilterOptions(
$filterSettings, $filterSettings,
$excludedConceptUids, $excludedConceptUids,
@@ -187,7 +159,7 @@ class SearchController extends ActionController
'searchresult', 'searchresult',
['filterSettings' => $filterSettings], ['filterSettings' => $filterSettings],
'Search', 'Search',
null, 'epproducts',
'searchresult' 'searchresult'
) )
; ;
@@ -208,11 +180,11 @@ class SearchController extends ActionController
/** /**
* @param string $encodedFilterSettings * @param string $encodedFilterSettings
* @throws StopActionException
*/ */
public function staticresultAction($encodedFilterSettings) public function staticresultAction($encodedFilterSettings)
{ {
$filterSettings = FilterSettingsEncoder::decode($encodedFilterSettings); $filterSettingsEncoder = new FilterSettingsEncoder();
$filterSettings = $filterSettingsEncoder->decode($encodedFilterSettings);
$this->forward( $this->forward(
'searchresult', 'searchresult',
@@ -1,503 +0,0 @@
<?php
namespace EP\EpProducts\Domain\Model;
/***************************************************************
*
* Copyright notice
*
* (c) 2016 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use EP\EpProducts\Utility\TypeConversionUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Request;
class FilterSettings implements \JsonSerializable
{
public const DURATION_SHORT = 4;
public const DURATION_LONG = 5;
/**
* @var array
*/
protected $priceRanges = [];
/**
* @var array
*/
protected $countryUids = [];
/**
* @var array
*/
protected $regionUids = [];
/**
* @var array
*/
protected $cityUids = [];
/**
* @var array
*/
protected $conceptUids = [];
/**
* @var int
*/
protected $hotelUid = 0;
/**
* @var int
*/
protected $productUid = 0;
/**
* @var \DateTime
*/
protected $dateFrom;
/**
* @var \DateTime
*/
protected $dateTo;
/**
* @var int
*/
protected $nights = 0;
/**
* @var int
*/
protected $pax = 0;
/**
* @var array
*/
protected $boardTypes = [];
/**
* @var array
*/
protected $hotelTypes = [];
/**
* @var array
*/
protected $roomTypes = [];
/**
* @var bool
*/
protected $bus;
public function __construct(\DateTime $dateFrom = null, \DateTime $dateTo = null)
{
if ($dateFrom !== null) {
$this->setDateFrom($dateFrom);
}
if ($dateTo !== null) {
$this->setDateTo($dateTo);
}
}
public static function fromSearchParams(SearchParams $searchParams): self
{
$filterSettings = new self;
if ($searchParams->getDestinationType() && $searchParams->getDestinationUid()) {
$destinationUid = $searchParams->getDestinationUid();
switch ($searchParams->getDestinationType()) {
case SearchParams::DESTINATION_TYPE_COUNTRY:
$filterSettings->setCountryUids([$destinationUid]);
break;
case SearchParams::DESTINATION_TYPE_REGION:
$filterSettings->setRegionUids([$destinationUid]);
break;
case SearchParams::DESTINATION_TYPE_CITY:
$filterSettings->setCityUids([$destinationUid]);
break;
case SearchParams::DESTINATION_TYPE_CONCEPT:
$filterSettings->setConceptUids([$destinationUid]);
break;
}
}
if (null !== $searchParams->getDateFrom()) {
$filterSettings->setDateFrom($searchParams->getDateFrom());
}
if (null !== $searchParams->getDateTo()) {
$filterSettings->setDateTo($searchParams->getDateTo());
}
if (null !== $searchParams->getPriceRange()) {
$filterSettings->setPriceRanges([$searchParams->getPriceRange()]);
}
if (null !== $searchParams->getPax()) {
$filterSettings->setPax($searchParams->getPax());
}
if (null !== $searchParams->getNights()) {
$filterSettings->setNights($searchParams->getNights());
}
return $filterSettings;
}
public static function fromStaticSearchParams(array $searchParams): self
{
$filterSettings = new self;
if (!empty($searchParams['country'])) {
$filterSettings->setCountryUids([$searchParams['country']]);
}
if (!empty($searchParams['region'])) {
$filterSettings->setRegionUids([$searchParams['region']]);
}
if (!empty($searchParams['concept'])) {
$conceptUids = GeneralUtility::trimExplode(',', $searchParams['concept']);
$filterSettings->setConceptUids($conceptUids);
}
if (!empty($searchParams['hotel'])) {
$filterSettings->setHotelUid($searchParams['hotel']);
}
if (!empty($searchParams['product'])) {
$filterSettings->setProductUid($searchParams['product']);
}
if (!empty($searchParams['hotel_types'])) {
$hotelTypes = explode(',', $searchParams['hotel_types']);
$filterSettings->setHotelTypes($hotelTypes);
}
if (!empty($searchParams['bus'])) {
$filterSettings->setBus((bool) $searchParams['bus']);
}
if (!empty($searchParams['date_from']) && $searchParams['date_from'] !== '0000-00-00') {
$dateFrom = new \DateTime($searchParams['date_from']);
$filterSettings->setDateFrom($dateFrom);
}
if (!empty($searchParams['date_to']) && $searchParams['date_to'] !== '0000-00-00') {
$dateTo = new \DateTime($searchParams['date_to']);
$filterSettings->setDateTo($dateTo);
}
if (!empty($searchParams['pax']) && (int) $searchParams['pax'] > 0) {
$filterSettings->setPax($searchParams['pax']);
}
return $filterSettings;
}
public static function fromRequest(Request $request): self
{
$requestArgument = $request->getArgument('filterSettings');
$filterSettings = is_array($requestArgument) ?
$requestArgument : json_decode($requestArgument, true, 512, JSON_THROW_ON_ERROR);
if ($requestArgument instanceof FilterSettings) {
$filterSettingsObject = $requestArgument;
} else {
$filterSettings['dateFrom'] = !empty($filterSettings['dateFrom']) ? new \DateTime($filterSettings['dateFrom']) : null;
$filterSettings['dateTo'] = !empty($filterSettings['dateTo']) ? new \DateTime($filterSettings['dateTo']) : null;
if (!empty($filterSettings['bus'])) {
$filterSettings['bus'] = \in_array($filterSettings['bus'], [ true, 'true', '1' ], true);
} else {
$filterSettings['bus'] = null;
}
$filterSettingsObject = new FilterSettings();
foreach ($filterSettings as $key => $value) {
$setter = 'set' . ucfirst($key);
$value = TypeConversionUtility::convertType($value);
if (method_exists($filterSettingsObject, $setter)) {
$filterSettingsObject->{$setter}($value);
}
}
}
return $filterSettingsObject;
}
/**
* @return array
*/
public function getPriceRanges()
{
return $this->priceRanges;
}
/**
* @param array $priceRanges
*/
public function setPriceRanges($priceRanges)
{
$this->priceRanges = $priceRanges;
}
/**
* @return array
*/
public function getCountryUids()
{
return $this->countryUids;
}
/**
* @param array $countryUids
*/
public function setCountryUids(?array $countryUids)
{
$this->countryUids = $countryUids;
}
/**
* @return array
*/
public function getRegionUids()
{
return $this->regionUids;
}
/**
* @param array $regionUids
*/
public function setRegionUids(?array $regionUids)
{
$this->regionUids = $regionUids;
}
/**
* @return array
*/
public function getCityUids()
{
return $this->cityUids;
}
/**
* @param array $cityUids
*/
public function setCityUids(?array $cityUids)
{
$this->cityUids = $cityUids;
}
/**
* @return array
*/
public function getConceptUids()
{
return $this->conceptUids;
}
/**
* @param array $conceptUids
*/
public function setConceptUids(?array $conceptUids)
{
$this->conceptUids = $conceptUids;
}
/**
* @return int
*/
public function getHotelUid()
{
return $this->hotelUid;
}
/**
* @param int $hotelUid
*/
public function setHotelUid($hotelUid)
{
$this->hotelUid = (int) $hotelUid;
}
/**
* @return int
*/
public function getProductUid()
{
return $this->productUid;
}
/**
* @param int $productUid
*/
public function setProductUid($productUid)
{
$this->productUid = (int) $productUid;
}
/**
* @return \DateTime
*/
public function getDateFrom()
{
return $this->dateFrom;
}
/**
* @param \DateTime $dateFrom
*/
public function setDateFrom(\DateTimeInterface $dateFrom = null)
{
$this->dateFrom = $dateFrom;
}
/**
* @return \DateTime
*/
public function getDateTo()
{
return $this->dateTo;
}
/**
* @param \DateTime $dateTo
*/
public function setDateTo(\DateTimeInterface $dateTo = null)
{
if ($this->dateFrom !== null && $dateTo !== null && $dateTo < $this->dateFrom) {
$dateTo = $this->dateFrom;
}
$this->dateTo = $dateTo;
}
/**
* @return int
*/
public function getNights() {
return $this->nights;
}
/**
* @param int $nights
*/
public function setNights($nights) {
$this->nights = (int) $nights;
}
/**
* @return int
*/
public function getPax() {
return $this->pax;
}
/**
* @param int $pax
*/
public function setPax($pax) {
$this->pax = (int) $pax;
}
/**
* @return array
*/
public function getBoardTypes()
{
return $this->boardTypes;
}
/**
* @param array $boardTypes
*/
public function setBoardTypes(?array $boardTypes)
{
$this->boardTypes = $boardTypes;
}
/**
* @return array
*/
public function getHotelTypes()
{
return $this->hotelTypes;
}
/**
* @param array $hotelTypes
*/
public function setHotelTypes(?array $hotelTypes)
{
$this->hotelTypes = $hotelTypes;
}
/**
* @return array
*/
public function getRoomTypes()
{
return $this->roomTypes;
}
/**
* @param array $roomTypes
*/
public function setRoomTypes(?array $roomTypes)
{
$this->roomTypes = $roomTypes;
}
/**
* @return bool
*/
public function getBus()
{
return $this->bus;
}
/**
* @param bool $bus
*/
public function setBus($bus)
{
$this->bus = $bus;
}
public function toArray(): array
{
return [
'priceRanges' => $this->getPriceRanges(),
'countryUids' => $this->getCountryUids(),
'regionUids' => $this->getRegionUids(),
'cityUids' => $this->getCityUids(),
'conceptUids' => $this->getConceptUids(),
'hotelUid' => $this->getHotelUid(),
'productUid' => $this->getProductUid(),
'nights' => $this->getNights(),
'pax' => $this->getPax(),
'boardTypes' => $this->getBoardTypes(),
'hotelTypes' => $this->getHotelTypes(),
'roomTypes' => $this->getRoomTypes(),
'dateFrom' => ($this->getDateFrom() !== null) ? $this->getDateFrom()->format('Y-m-d') : null,
'dateTo' => ($this->getDateTo() !== null) ? $this->getDateTo()->format('Y-m-d') : null,
'bus' => $this->getBus(),
];
}
public function jsonSerialize()
{
return $this->toArray();
}
}
@@ -1,297 +0,0 @@
<?php
namespace EP\EpProducts\Domain\Model;
/***************************************************************
*
* Copyright notice
*
* (c) 2017 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use EP\EpProducts\Utility\TypeConversionUtility;
use TYPO3\CMS\Extbase\Mvc\Request;
class SearchParams implements \JsonSerializable
{
public const DESTINATION_TYPE_COUNTRY = 'country';
public const DESTINATION_TYPE_REGION = 'region';
public const DESTINATION_TYPE_CITY = 'city';
public const DESTINATION_TYPE_CONCEPT = 'concept';
/**
* @var int
*/
protected $priceRange;
/**
* @var \DateTime
*/
protected $dateFrom;
/**
* @var \DateTime
*/
protected $dateTo;
/**
* @var int
*/
protected $destinationUid;
/**
* @var string
*/
protected $destinationType;
/**
* @var int
*/
protected $nights;
/**
* @var int
*/
protected $pax = 1;
/**
* @var int
*/
protected $pageUid;
/**
* @param int $pageUid
*/
public function __construct(int $pageUid = 0)
{
$this->setPageUid($pageUid);
}
public static function fromRequest(Request $request): self
{
$requestArgument = $request->getArgument('searchParams');
$searchParams = is_array($requestArgument) ?
$requestArgument : json_decode($requestArgument, true, 512, JSON_THROW_ON_ERROR);
$searchParams['dateFrom'] = !empty($searchParams['dateFrom']) ? new \DateTime($searchParams['dateFrom']) : null;
$searchParams['dateTo'] = !empty($searchParams['dateTo']) ? new \DateTime($searchParams['dateTo']) : null;
$searchParamsObject = new self;
foreach ($searchParams as $key => $value) {
$setter = 'set' . ucfirst($key);
$value = TypeConversionUtility::convertType($value);
if (method_exists($searchParamsObject, $setter)) {
$searchParamsObject->{$setter}($value);
}
}
return $searchParamsObject;
}
/**
* @return int
*/
public function getPriceRange()
{
return $this->priceRange;
}
/**
* @param int $priceRange
*/
public function setPriceRange($priceRange)
{
$this->priceRange = $priceRange;
}
/**
* @return \DateTime
*/
public function getDateFrom()
{
return $this->dateFrom;
}
/**
* @param \DateTime $dateFrom
*/
public function setDateFrom(\DateTime $dateFrom = null)
{
$this->dateFrom = $dateFrom;
}
/**
* @return \DateTime
*/
public function getDateTo()
{
return $this->dateTo;
}
/**
* @param \DateTime $dateTo
*/
public function setDateTo(\DateTime $dateTo = null)
{
$this->dateTo = $dateTo;
}
/**
* @return int
*/
public function getDestinationUid()
{
return $this->destinationUid;
}
/**
* @param int $destinationUid
*/
public function setDestinationUid($destinationUid)
{
$this->destinationUid = $destinationUid;
}
/**
* @return string
*/
public function getDestinationType()
{
return $this->destinationType;
}
/**
* @param string $destinationType
*/
public function setDestinationType($destinationType)
{
$this->destinationType = $destinationType;
}
/**
* @return int
*/
public function getNights()
{
return $this->nights;
}
/**
* @param int $nights
*/
public function setNights($nights)
{
$this->nights = $nights;
}
/**
* @return int
*/
public function getPax()
{
return $this->pax;
}
/**
* @param int $pax
*/
public function setPax($pax)
{
$this->pax = $pax;
}
/**
* @return int
*/
public function getPageUid()
{
return $this->pageUid;
}
/**
* @param int $pageUid
*/
public function setPageUid($pageUid)
{
$this->pageUid = $pageUid;
}
public function toFilterSettings(): array
{
$filterSettings = [];
if ($this->getDestinationType() && $this->getDestinationUid()) {
$destinationUid = $this->getDestinationUid();
switch ($this->getDestinationType()) {
case SearchParams::DESTINATION_TYPE_COUNTRY:
$filterSettings['countryUids'] = [ $destinationUid ];
break;
case SearchParams::DESTINATION_TYPE_REGION:
$filterSettings['regionUids'] = [ $destinationUid ];
break;
case SearchParams::DESTINATION_TYPE_CITY:
$filterSettings['cityUids'] = [ $destinationUid ];
break;
case SearchParams::DESTINATION_TYPE_CONCEPT:
$filterSettings['conceptUids'] = [ $destinationUid ];
break;
}
}
if (null !== $this->getDateFrom()) {
$filterSettings['dateFrom'] = $this->getDateFrom()->format('Y-m-d');
}
if (null !== $this->getDateTo()) {
$filterSettings['dateTo'] = $this->getDateTo()->format('Y-m-d');
}
if (null !== $this->getPriceRange()) {
$filterSettings['priceRanges'] = [ $this->getPriceRange() ];
}
if (null !== $this->getPax()) {
$filterSettings['pax'] = $this->getPax();
}
if (null !== $this->getNights()) {
$filterSettings['nights'] = $this->getNights();
}
return $filterSettings;
}
public function toArray(): array
{
return [
'priceRange' => $this->getPriceRange(),
'destinationUid' => $this->getDestinationUid(),
'destinationType' => $this->getDestinationType(),
'nights' => $this->getNights(),
'pax' => $this->getPax(),
'pageUid' => $this->getPageUid(),
'dateFrom' => ($this->getDateFrom() !== null) ? $this->getDateFrom()->format('Y-m-d') : null,
'dateTo' => ($this->getDateTo() !== null) ? $this->getDateTo()->format('Y-m-d') : null,
];
}
public function jsonSerialize()
{
return $this->toArray();
}
}
@@ -28,7 +28,7 @@ namespace EP\EpProducts\Domain\Repository;
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\FilterOptions; use EP\EpProducts\Domain\Model\FilterOptions;
use EP\EpProducts\Domain\Model\FilterSettings; use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Traits\DbConnectionTrait; use EP\EpProducts\Traits\DbConnectionTrait;
use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\Query\QueryBuilder;
@@ -165,12 +165,12 @@ abstract class AbstractRepository extends \TYPO3\CMS\Extbase\Persistence\Reposit
return; return;
} }
$nights = (int)$filterSettings['nights']; $nights = (int)$filterSettings['nights'];
if ($nights === FilterSettings::DURATION_SHORT) { if ($nights === FilterService::DURATION_SHORT) {
$query $query
->andWhere('date.nights <= :nights') ->andWhere('date.nights <= :nights')
->setParameter('nights', $nights) ->setParameter('nights', $nights)
; ;
} elseif ($nights === FilterSettings::DURATION_LONG) { } elseif ($nights === FilterService::DURATION_LONG) {
$query $query
->andWhere('date.nights >= :nights') ->andWhere('date.nights >= :nights')
->setParameter('nights', $nights) ->setParameter('nights', $nights)
@@ -27,7 +27,7 @@ namespace EP\EpProducts\Middleware;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\FilterSettings; use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\FilterSettingsEncoder; use EP\EpProducts\Service\FilterSettingsEncoder;
use EP\EpProducts\Traits\DbConnectionTrait; use EP\EpProducts\Traits\DbConnectionTrait;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
@@ -36,6 +36,8 @@ use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Http\NormalizedParams; use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Http\RedirectResponse; use TYPO3\CMS\Core\Http\RedirectResponse;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
class StaticSearchParamsMiddleware implements MiddlewareInterface class StaticSearchParamsMiddleware implements MiddlewareInterface
{ {
@@ -78,8 +80,15 @@ class StaticSearchParamsMiddleware implements MiddlewareInterface
return $handler->handle($request); return $handler->handle($request);
} }
$filterSettings = FilterSettings::fromStaticSearchParams($row); /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
$encodedFilterSettings = rawurlencode(FilterSettingsEncoder::encode($filterSettings)); $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** @var \EP\EpProducts\Service\FilterService $filterService */
$filterService = $objectManager->get(FilterService::class);
$filterSettings = $filterService->getFilterSettingsFromStaticSearchParams($row);
$filterSettingsEncoder = new FilterSettingsEncoder();
$encodedFilterSettings = rawurlencode($filterSettingsEncoder->encode($filterSettings));
$redirectUri = self::SEARCH_RESULT_URI . $encodedFilterSettings . '/'; $redirectUri = self::SEARCH_RESULT_URI . $encodedFilterSettings . '/';
@@ -27,7 +27,6 @@ namespace EP\EpProducts\Service;
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\Date; use EP\EpProducts\Domain\Model\Date;
use EP\EpProducts\Domain\Model\FilterSettings;
use EP\EpProducts\Domain\Model\Hotel; use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Model\Product; use EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Domain\Repository\DateRepository; use EP\EpProducts\Domain\Repository\DateRepository;
@@ -112,7 +111,7 @@ class DateService implements SingletonInterface
$paCode = $resolvedOptions['paCode']; $paCode = $resolvedOptions['paCode'];
if ($filterSettings === null) { if ($filterSettings === null) {
$filterSettings = $this->filterService->getDefaultFilterSettings(); $filterSettings = $this->filterService->getNormalizedFilterSettings();
} }
$nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true); $nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true);
@@ -152,7 +151,7 @@ class DateService implements SingletonInterface
$paCode = $resolvedOptions['paCode']; $paCode = $resolvedOptions['paCode'];
if ($filterSettings === null) { if ($filterSettings === null) {
$filterSettings = $this->filterService->getDefaultFilterSettings(); $filterSettings = $this->filterService->getNormalizedFilterSettings();
} }
$nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true); $nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true);
@@ -196,67 +195,6 @@ class DateService implements SingletonInterface
]); ]);
} }
public function getAutocompleteOptions
(
FilterSettings $filterSettings,
string $search,
array $excludedConceptUids = [],
array $excludedRegionUids = []
)
{
$destinations = [
'country' => [
'label' => 'Länder',
'options' => [],
],
'region' => [
'label' => 'Gebiete',
'options' => [],
],
'city' => [
'label' => 'Orte',
'options' => [],
],
'hotel' => [
'label' => 'Unterkünfte',
'options' => [],
],
'product' => [
'label' => 'Reiseangebote',
'options' => [],
],
];
$search = mb_strtolower(trim($search));
foreach ($destinations as $type => $dummy) {
switch ($type) {
case 'hotel':
$result = $this->dateRepository->searchHotels($search, $filterSettings);
break;
case 'product':
$result = $this->dateRepository->searchProducts($search, $filterSettings, $excludedConceptUids);
break;
default:
$result = $this->dateRepository->searchDestinations(
$type,
$search,
$filterSettings,
$excludedRegionUids
);
}
foreach ($result as $row) {
$destinations[$type]['options'][] = [
'uid' => $row['uid'],
'name' => $row['name'],
];
}
}
return $destinations;
}
public function preprocessPriceTable(array $options): array public function preprocessPriceTable(array $options): array
{ {
$resolver = new OptionsResolver(); $resolver = new OptionsResolver();
@@ -28,8 +28,8 @@ namespace EP\EpProducts\Service;
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\FilterOptions; use EP\EpProducts\Domain\Model\FilterOptions;
use EP\EpProducts\Domain\Model\FilterSettings;
use EP\EpProducts\Domain\Repository\DateRepository; use EP\EpProducts\Domain\Repository\DateRepository;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -37,6 +37,9 @@ use TYPO3\CMS\Core\Utility\PathUtility;
class FilterService implements SingletonInterface class FilterService implements SingletonInterface
{ {
public const DURATION_SHORT = 4;
public const DURATION_LONG = 5;
/** /**
* @var array * @var array
*/ */
@@ -55,29 +58,108 @@ class FilterService implements SingletonInterface
$this->dateRepository = $dateRepository; $this->dateRepository = $dateRepository;
} }
public function getDefaultFilterSettings public function getNormalizedFilterSettings(array $filterSettings = [], array $forcedConceptUids = []): array
( {
array $forcedConceptUids = [], $optionsResolver = new OptionsResolver();
bool $respectBookableDaterange = true, $optionsResolver->setDefaults([
string $dateFromYmd = null, 'priceRanges' => [],
string $dateToYmd = null 'countryUids' => [],
): array { 'regionUids' => [],
$filterSettings = []; 'cityUids' => [],
'conceptUids' => [],
'boardTypes' => [],
'hotelTypes' => [],
'roomTypes' => [],
'hotelUid' => 0,
'productUid' => 0,
'dateFrom' => null,
'dateTo' => null,
'nights' => 0,
'pax' => 0,
'bus' => null,
]);
[ $dateFrom, $dateTo ] = $this->constrainDateRange($this->dateRepository->getDateRange()); $filterSettings = $optionsResolver->resolve($filterSettings);
if (null !== $dateFromYmd) { if (count($forcedConceptUids) > 0) {
$filterSettings['dateFrom'] = $dateFromYmd; array_push($filterSettings['conceptUids'], ...$forcedConceptUids);
} elseif (!$respectBookableDaterange) {
$dateFrom = (new \DateTime('now'))->format('Y-m-d');
} }
if (null !== $dateToYmd) { return $filterSettings;
$filterSettings['dateTo'] = $dateToYmd; }
public function getFilterSettingsFromSearchParams(array $searchParams): array
{
$filterSettings = $this->getNormalizedFilterSettings();
if ($searchParams['destinationType'] && $searchParams['destinationUid']) {
switch ($searchParams['destinationType']) {
case SearchResultService::DESTINATION_TYPE_COUNTRY:
$filterSettings['countryUids'] = [ $searchParams['destinationUid'] ];
break;
case SearchResultService::DESTINATION_TYPE_REGION:
$filterSettings['regionUids'] = [ $searchParams['destinationUid'] ];
break;
case SearchResultService::DESTINATION_TYPE_CITY:
$filterSettings['cityUids'] = [ $searchParams['destinationUid'] ];
break;
case SearchResultService::DESTINATION_TYPE_CONCEPT:
$filterSettings['conceptUids'] = [ $searchParams['destinationUid'] ];
break;
}
}
if ($searchParams['dateFrom']) {
$filterSettings['dateFrom'] = $searchParams['dateFrom'];
}
if ($searchParams['dateTo']) {
$filterSettings['dateTo'] = $searchParams['dateTo'];
}
if ($searchParams['priceRange']) {
$filterSettings['priceRanges'] = [ $searchParams['priceRange'] ];
}
if ($searchParams['pax']) {
$filterSettings['pax'] = $searchParams['pax'];
}
if ($searchParams['nights']) {
$filterSettings['nights'] = $searchParams['nights'];
} }
if ($forcedConceptUids) { return $filterSettings;
$filterSettings['conceptUids'] = $forcedConceptUids; }
public function getFilterSettingsFromStaticSearchParams(array $staticSearchParams): array
{
$filterSettings = $this->getNormalizedFilterSettings();
if (!empty($staticSearchParams['country'])) {
$filterSettings['countryUids'] = [ $staticSearchParams['country'] ];
}
if (!empty($staticSearchParams['region'])) {
$filterSettings['regionUids'] = [ $staticSearchParams['region'] ];
}
if (!empty($staticSearchParams['concept'])) {
$filterSettings['conceptUids'] = GeneralUtility::trimExplode(',', $staticSearchParams['concept'], true);
}
if (!empty($staticSearchParams['hotel'])) {
$filterSettings['hotelUid'] = $staticSearchParams['hotel'];
}
if (!empty($staticSearchParams['product'])) {
$filterSettings['productUid'] = $staticSearchParams['product'];
}
if (!empty($staticSearchParams['hotel_types'])) {
$filterSettings['hotelTypes'] = GeneralUtility::trimExplode(',', $staticSearchParams['hotel_types'], true);
}
if (!empty($staticSearchParams['bus'])) {
$filterSettings['bus'] = (bool)$staticSearchParams['bus'];
}
if (!empty($staticSearchParams['date_from']) && $staticSearchParams['date_from'] !== '0000-00-00') {
$filterSettings['dateFrom'] = $staticSearchParams['date_from'];
}
if (!empty($staticSearchParams['date_to']) && $staticSearchParams['date_to'] !== '0000-00-00') {
$filterSettings['dateTo'] = $staticSearchParams['date_to'];
}
if (!empty($staticSearchParams['pax']) && (int)$staticSearchParams['pax'] > 0) {
$filterSettings['pax'] = $staticSearchParams['pax'];
} }
return $filterSettings; return $filterSettings;
@@ -118,8 +200,7 @@ class FilterService implements SingletonInterface
$absPath = ExtensionManagementUtility::extPath('ep_theme'); $absPath = ExtensionManagementUtility::extPath('ep_theme');
$baseUrl = '/' . PathUtility::stripPathSitePrefix($absPath); $baseUrl = '/' . PathUtility::stripPathSitePrefix($absPath);
foreach ($filterOptionsData as $row) foreach ($filterOptionsData as $row) {
{
// Date // Date
if (null === $dateMin || $row['dateStart'] < $dateMin) { if (null === $dateMin || $row['dateStart'] < $dateMin) {
$dateMin = $row['dateStart']; $dateMin = $row['dateStart'];
@@ -134,7 +215,7 @@ class FilterService implements SingletonInterface
} }
// Nights // Nights
$duration = (int) $row['nights'] <= 4 ? FilterSettings::DURATION_SHORT : FilterSettings::DURATION_LONG; $duration = (int) $row['nights'] <= 4 ? self::DURATION_SHORT : self::DURATION_LONG;
if (!\in_array($duration, $nights, false)) { if (!\in_array($duration, $nights, false)) {
$nights[] = $duration; $nights[] = $duration;
@@ -235,25 +316,9 @@ class FilterService implements SingletonInterface
return $filterOptions; return $filterOptions;
} }
public function constrainDateRange(array $dateRange, FilterSettings $filterSettings = null): array
{
[ $dateFrom, $dateTo ] = $dateRange;
if ($filterSettings !== null) {
$filterSettingsDateFrom = $filterSettings->getDateFrom();
if ($filterSettingsDateFrom !== null && $filterSettingsDateFrom > $dateFrom) {
$dateFrom = $filterSettings->getDateFrom();
}
$filterSettingsDateTo = $filterSettings->getDateTo();
if ($filterSettingsDateTo !== null && $filterSettingsDateTo < $dateTo) {
$dateTo = $filterSettings->getDateTo();
}
}
return [$dateFrom, $dateTo];
}
public function getFiltersettingsFromSettings(array $settings): array public function getFiltersettingsFromSettings(array $settings): array
{ {
$filterSettings = []; $filterSettings = $this->getNormalizedFilterSettings();
if (isset($settings['regionUid']) && (int) $settings['regionUid'] > 0) { if (isset($settings['regionUid']) && (int) $settings['regionUid'] > 0) {
$filterSettings['regionUids'] = [ $settings['regionUid'] ]; $filterSettings['regionUids'] = [ $settings['regionUid'] ];
} elseif (isset($settings['countryUid']) && (int) $settings['countryUid'] > 0) { } elseif (isset($settings['countryUid']) && (int) $settings['countryUid'] > 0) {
@@ -34,7 +34,7 @@ class FilterSettingsEncoder
/** /**
* @return array * @return array
*/ */
protected static function getMapping() protected function getMapping()
{ {
return [ return [
[ 'countryUids', 'array' ], [ 'countryUids', 'array' ],
@@ -54,10 +54,10 @@ class FilterSettingsEncoder
]; ];
} }
public static function encode(array $filterSettings): string public function encode(array $filterSettings): string
{ {
$encoded = []; $encoded = [];
foreach (static::getMapping() as $property) { foreach ($this->getMapping() as $property) {
[$name, $type] = $property; [$name, $type] = $property;
if (!isset($filterSettings[$name])) { if (!isset($filterSettings[$name])) {
$encoded[] = ''; $encoded[] = '';
@@ -76,11 +76,11 @@ class FilterSettingsEncoder
return implode('|', $encoded); return implode('|', $encoded);
} }
public static function decode(string $data): array public function decode(string $data): array
{ {
$decoded = []; $decoded = [];
$values = explode('|', $data); $values = explode('|', $data);
foreach (static::getMapping() as $index => $property) foreach ($this->getMapping() as $index => $property)
{ {
[$name, $type] = $property; [$name, $type] = $property;
$value = $values[$index]; $value = $values[$index];
@@ -26,7 +26,6 @@ namespace EP\EpProducts\Service;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\FilterSettings;
use EP\EpProducts\Domain\Model\Hotel; use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Repository\HotelRepository; use EP\EpProducts\Domain\Repository\HotelRepository;
use EP\EpProducts\Domain\Repository\ProductRepository; use EP\EpProducts\Domain\Repository\ProductRepository;
@@ -83,11 +82,11 @@ class ProductDataService implements SingletonInterface
} }
/** /**
* @param FilterSettings $filterSettings * @param array $filterSettings
* *
* @return array * @return array
*/ */
public function getTeaser(FilterSettings $filterSettings) public function getTeaser(array $filterSettings)
{ {
$teaserData = $this->productRepository->getTeasers($filterSettings); $teaserData = $this->productRepository->getTeasers($filterSettings);
$teasers = $this->preprocessTeasergroup($teaserData); $teasers = $this->preprocessTeasergroup($teaserData);
@@ -141,15 +140,12 @@ class ProductDataService implements SingletonInterface
$teaser['busIncluded'] = true; $teaser['busIncluded'] = true;
} }
$dateStart = new \DateTime($row['dateStart']); if ($teaser['minDateStart'] === null || $teaser['minDateStart'] > $row['dateStart']) {
$dateEnd = new \DateTime($row['dateEnd']); $teaser['minDateStart'] = $row['dateStart'];
if ($teaser['minDateStart'] === null || $teaser['minDateStart'] > $dateStart) {
$teaser['minDateStart'] = $dateStart;
} }
if ($teaser['maxDateEnd'] === null || $teaser['maxDateEnd'] < $dateEnd) { if ($teaser['maxDateEnd'] === null || $teaser['maxDateEnd'] < $row['dateEnd']) {
$teaser['maxDateEnd'] = $dateEnd; $teaser['maxDateEnd'] = $row['dateEnd'];
} }
if ($teaser['minPrice'] === null || $teaser['minPrice'] > $row['minPrice']) { if ($teaser['minPrice'] === null || $teaser['minPrice'] > $row['minPrice']) {
@@ -26,14 +26,19 @@ namespace EP\EpProducts\Service;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\FilterSettings;
use EP\EpProducts\Domain\Model\Hotel; use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Model\SearchResult; use EP\EpProducts\Domain\Model\SearchResult;
use EP\EpProducts\Domain\Repository\ProductRepository; use EP\EpProducts\Domain\Repository\ProductRepository;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\SingletonInterface;
class SearchResultService implements SingletonInterface class SearchResultService implements SingletonInterface
{ {
public const DESTINATION_TYPE_COUNTRY = 'country';
public const DESTINATION_TYPE_REGION = 'region';
public const DESTINATION_TYPE_CITY = 'city';
public const DESTINATION_TYPE_CONCEPT = 'concept';
/** /**
* @var ProductRepository * @var ProductRepository
*/ */
@@ -66,6 +71,23 @@ class SearchResultService implements SingletonInterface
$this->flagIconService = $flagIconService; $this->flagIconService = $flagIconService;
} }
public function getNormalizedSearchParams(array $searchParams = []): array
{
$optionsResolver = new OptionsResolver();
$optionsResolver->setDefaults([
'priceRange' => null,
'dateFrom' => null,
'dateTo' => null,
'destinationUid' => null,
'destinationType' => null,
'nights' => null,
'pax' => 1,
'pageUid' => null,
]);
return $optionsResolver->resolve($searchParams);
}
public function getResult(array $filterSettings, array $excludedConceptUids = []): SearchResult public function getResult(array $filterSettings, array $excludedConceptUids = []): SearchResult
{ {
$searchResultData = $this->productRepository->getSearchResult($filterSettings, $excludedConceptUids); $searchResultData = $this->productRepository->getSearchResult($filterSettings, $excludedConceptUids);
@@ -1,96 +0,0 @@
<?php
namespace EP\EpProducts\Service;
/***************************************************************
*
* Copyright notice
*
* (c) 2018 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use EP\EpProducts\Domain\Model\SearchResult;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
class SearchResultUrlService implements SingletonInterface
{
/**
* @var UriBuilder
*/
protected $uriBuilder;
/**
* @param UriBuilder $uriBuilder
*/
public function __construct(UriBuilder $uriBuilder)
{
$this->uriBuilder = $uriBuilder;
}
/**
* @param SearchResult $searchResult
* @param int $defaultDetailPageUid
*/
public function process(SearchResult $searchResult, $defaultDetailPageUid): void
{
$data = $searchResult->getData();
foreach ($data as $conceptUid => $conceptData) {
foreach ($conceptData['items'] as $key => $item) {
$detailPageUid = $this->getProductPageUid($item['product']['detailPage'], $defaultDetailPageUid);
$arguments = [
'product' => $item['product']['uid'],
'hotel' => $item['hotel']['uid'],
];
$uri = $this
->uriBuilder
->reset()
->setTargetPageUid($detailPageUid)
->setCreateAbsoluteUri(true)
->uriFor('detail', $arguments, 'Product', 'epproducts', 'product_detail')
;
$data[$conceptUid]['items'][$key]['detailPageUri'] = $uri;
}
}
$searchResult->setData($data);
}
/**
* @param mixed $uid
* @param int $defaultUid
* @return int
*/
protected function getProductPageUid($uid, int $defaultUid): int
{
if (is_numeric($uid)) {
return (int) $uid;
}
if (strpos($uid, 't3://') !== false) {
parse_str(parse_url($uid, PHP_URL_QUERY), $arguments);
return (int) $arguments['uid'];
}
return $defaultUid;
}
}
@@ -27,9 +27,8 @@ namespace EP\EpProducts\Traits;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use EP\EpProducts\Domain\Model\FilterSettings;
use EP\EpProducts\Domain\Model\SearchParams;
use EP\EpProducts\Service\FilterService; use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\SearchResultService;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager; use TYPO3\CMS\Extbase\Object\ObjectManager;
@@ -41,11 +40,22 @@ trait RequestArgumentTypeConversionTrait
*/ */
public function processSearchParamsArgument() public function processSearchParamsArgument()
{ {
if ($this->request->hasArgument('searchParams')) { /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
$searchParamsObject = SearchParams::fromRequest($this->request); $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$this->request->setArgument('searchParams', $searchParamsObject); /** @var SearchResultService $searchResultService */
$searchResultService = $objectManager->get(SearchResultService::class);
if (!$this->request->hasArgument('searchParams')) {
$searchParams = $searchResultService->getNormalizedSearchParams();
} else {
$requestArgument = $this->request->getArgument('searchParams');
$searchParamsFromRequest = is_array($requestArgument) ?
$requestArgument : json_decode($requestArgument, true, 512, JSON_THROW_ON_ERROR);
$searchParams = $searchResultService->getNormalizedSearchParams($searchParamsFromRequest);
} }
$this->request->setArgument('searchParams', $searchParams);
} }
/** /**
@@ -61,18 +71,15 @@ trait RequestArgumentTypeConversionTrait
$filterService = $objectManager->get(FilterService::class); $filterService = $objectManager->get(FilterService::class);
if (!$this->request->hasArgument('filterSettings')) { if (!$this->request->hasArgument('filterSettings')) {
$filterSettingsObject = $filterService->getDefaultFilterSettings($forcedConceptUids); $filterSettings = $filterService->getNormalizedFilterSettings([], $forcedConceptUids);
} else { } else {
$filterSettingsObject = FilterSettings::fromRequest($this->request); $requestArgument = $this->request->getArgument('filterSettings');
$filterSettingsFromRequest = is_array($requestArgument) ?
if ($forcedConceptUids) { $requestArgument : json_decode($requestArgument, true, 512, JSON_THROW_ON_ERROR);
$conceptUids = $filterSettingsObject->getConceptUids(); $filterSettings = $this->filterService->getNormalizedFilterSettings($filterSettingsFromRequest, $forcedConceptUids);
array_push($conceptUids, ...$forcedConceptUids);
$filterSettingsObject->setConceptUids($conceptUids);
}
} }
$this->request->setArgument('filterSettings', $filterSettingsObject); $this->request->setArgument('filterSettings', $filterSettings);
} }
} }
@@ -28,7 +28,6 @@ namespace EP\EpTheme\ViewHelpers;
***************************************************************/ ***************************************************************/
use EP\EpProducts\Service\FilterSettingsEncoder; use EP\EpProducts\Service\FilterSettingsEncoder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
@@ -69,8 +68,8 @@ class FilterSettingsEncoderViewHelper extends AbstractViewHelper
return ''; return '';
} }
/** @var \EP\EpProducts\Service\FilterSettingsEncoder $encoder */ $encoder = new FilterSettingsEncoder();
$encoder = GeneralUtility::makeInstance(FilterSettingsEncoder::class);
return $encoder::encode($argument); return $encoder->encode($argument);
} }
} }
@@ -21,6 +21,9 @@
</f:if> </f:if>
</div> </div>
<div class="searchresult__content"> <div class="searchresult__content">
<f:if condition="{referringPageUri}">
<f:variable name="referrer" value="{referringPageUri}"/>
</f:if>
<f:for each="{searchResult}" as="section"> <f:for each="{searchResult}" as="section">
<div id="concept-{section.concept.uid}"> <div id="concept-{section.concept.uid}">
<h2 class="headline headline--secondary headline--underlined"> <h2 class="headline headline--secondary headline--underlined">
@@ -31,7 +34,11 @@
<div class="teaserbox teaserbox--landscape teaserbox--searchresult searchresult-item"> <div class="teaserbox teaserbox--landscape teaserbox--searchresult searchresult-item">
<div class="teaserbox__image"> <div class="teaserbox__image">
<img src="{item.country.flag}" class="teaserbox__flag" alt="{item.country.name}"/> <img src="{item.country.flag}" class="teaserbox__flag" alt="{item.country.name}"/>
<a href="{item.detailPageUri}" rel="nofollow" title="Details &amp; Buchen"> <f:link.action action="detail" controller="Product" extensionName="epproducts" pluginName="product_detail"
arguments="{product: item.product.uid, hotel: item.hotel.uid}"
additionalParams="{ref: referrer}"
pageUid="{item.product.detailPage}" absolute="1"
rel="nofollow" title="Details &amp; Buchen">
<picture class="searchresult-item__image"> <picture class="searchresult-item__image">
<source media="(min-width: 1201px)" data-srcset="{item.hotel.images.resized.m.0.url} 260w"/> <source media="(min-width: 1201px)" data-srcset="{item.hotel.images.resized.m.0.url} 260w"/>
<source media="(max-width: 1200px)" data-srcset="{item.hotel.images.resized.l.0.url} 280w"/> <source media="(max-width: 1200px)" data-srcset="{item.hotel.images.resized.l.0.url} 280w"/>
@@ -40,7 +47,7 @@
title="{item.hotel.images.resized.s.0.title}" title="{item.hotel.images.resized.s.0.title}"
alt="{item.hotel.images.resized.s.0.alt}"> alt="{item.hotel.images.resized.s.0.alt}">
</picture> </picture>
</a> </f:link.action>
<div class="concept-badge concept-badge--{section.concept.code}" <div class="concept-badge concept-badge--{section.concept.code}"
id="{section.concept.code}"> id="{section.concept.code}">
{section.concept.name} {section.concept.name}
@@ -106,11 +113,15 @@
<f:if condition="{item.dates -> f:count()} > 0"> <f:if condition="{item.dates -> f:count()} > 0">
<f:if condition="{item.daytrip}"> <f:if condition="{item.daytrip}">
<f:else> <f:else>
<a href="{item.detailPageUri}{f:if(condition: referringPageUri, then: '?ref={referringPageUri -> f:format.urlencode()}')}" rel="nofollow" title="Details &amp; Buchen"> <f:link.action action="detail" controller="Product" extensionName="epproducts" pluginName="product_detail"
<span class="searchresult-item__daterange"> arguments="{product: item.product.uid, hotel: item.hotel.uid}"
<i class="fa fa-arrow-circle-right"></i> {item.dates -> f:count()} {f:if(condition: '{item.dates -> f:count()} > 1', then: 'Termine', else: 'Termin')} additionalParams="{ref: referrer}"
</span> pageUid="{item.product.detailPage}" absolute="1"
</a> rel="nofollow" title="Details &amp; Buchen">
<span class="searchresult-item__daterange">
<i class="fa fa-arrow-circle-right"></i> {item.dates -> f:count()} {f:if(condition: '{item.dates -> f:count()} > 1', then: 'Termine', else: 'Termin')}
</span>
</f:link.action>
</f:else> </f:else>
</f:if> </f:if>
</f:if> </f:if>
@@ -121,10 +132,14 @@
</div> </div>
<div class="teaserbox__aside__bottom"> <div class="teaserbox__aside__bottom">
<span class="searchresult-item__price">ab {item.minPrice} €</span> <span class="searchresult-item__price">ab {item.minPrice} €</span>
<a href="{item.detailPageUri}{f:if(condition: referringPageUri, then: '?ref={referringPageUri -> f:format.urlencode()}')}" class="button button--full searchresult-item__button" <f:link.action action="detail" controller="Product" extensionName="epproducts" pluginName="product_detail"
rel="nofollow" title="Details"> arguments="{product: item.product.uid, hotel: item.hotel.uid}"
additionalParams="{ref: referrer}"
pageUid="{item.product.detailPage}" absolute="1"
rel="nofollow" title="Details &amp; Buchen"
class="button w-full">
Details{f:if(condition: item.hideBookingButton, else: ' &amp; Buchen')} Details{f:if(condition: item.hideBookingButton, else: ' &amp; Buchen')}
</a> </f:link.action>
</div> </div>
</div> </div>
<button class="teaserbox-watchlist" x-data="watchlistToggle('{item.key}')" x-bind="trigger"> <button class="teaserbox-watchlist" x-data="watchlistToggle('{item.key}')" x-bind="trigger">
@@ -10,6 +10,9 @@
</div> </div>
</f:then> </f:then>
<f:else> <f:else>
<f:if condition="{referringPageUri}">
<f:variable name="referrer" value="{referringPageUri}"/>
</f:if>
<f:for each="{watchlist}" as="section"> <f:for each="{watchlist}" as="section">
<div id="concept-{section.concept.uid}"> <div id="concept-{section.concept.uid}">
<h2 class="headline headline--secondary headline--underlined"> <h2 class="headline headline--secondary headline--underlined">
@@ -20,7 +23,11 @@
<div class="teaserbox teaserbox--landscape teaserbox--searchresult searchresult-item"> <div class="teaserbox teaserbox--landscape teaserbox--searchresult searchresult-item">
<div class="teaserbox__image"> <div class="teaserbox__image">
<img src="{item.country.flag}" class="teaserbox__flag" alt="{item.country.name}"/> <img src="{item.country.flag}" class="teaserbox__flag" alt="{item.country.name}"/>
<a href="{item.detailPageUri}" rel="nofollow" title="Details &amp; Buchen"> <f:link.action action="detail" controller="Product" extensionName="epproducts" pluginName="product_detail"
arguments="{product: item.product.uid, hotel: item.hotel.uid}"
additionalParams="{ref: referrer}"
pageUid="{item.product.detailPage}" absolute="1"
rel="nofollow" title="Details &amp; Buchen">
<picture class="searchresult-item__image"> <picture class="searchresult-item__image">
<source media="(min-width: 1201px)" data-srcset="{item.hotel.images.resized.m.0.url} 260w"/> <source media="(min-width: 1201px)" data-srcset="{item.hotel.images.resized.m.0.url} 260w"/>
<source media="(max-width: 1200px)" data-srcset="{item.hotel.images.resized.l.0.url} 280w"/> <source media="(max-width: 1200px)" data-srcset="{item.hotel.images.resized.l.0.url} 280w"/>
@@ -29,7 +36,7 @@
title="{item.hotel.images.resized.s.0.title}" title="{item.hotel.images.resized.s.0.title}"
alt="{item.hotel.images.resized.s.0.alt}"> alt="{item.hotel.images.resized.s.0.alt}">
</picture> </picture>
</a> </f:link.action>
<div class="concept-badge concept-badge--{section.concept.code}" <div class="concept-badge concept-badge--{section.concept.code}"
id="{section.concept.code}"> id="{section.concept.code}">
{section.concept.name} {section.concept.name}
@@ -95,11 +102,15 @@
<f:if condition="{item.dates -> f:count()} > 0"> <f:if condition="{item.dates -> f:count()} > 0">
<f:if condition="{item.daytrip}"> <f:if condition="{item.daytrip}">
<f:else> <f:else>
<a href="{item.detailPageUri}{f:if(condition: referringPageUri, then: '?ref={referringPageUri -> f:format.urlencode()}')}" rel="nofollow" title="Details &amp; Buchen"> <f:link.action action="detail" controller="Product" extensionName="epproducts" pluginName="product_detail"
arguments="{product: item.product.uid, hotel: item.hotel.uid}"
additionalParams="{ref: referrer}"
pageUid="{item.product.detailPage}" absolute="1"
rel="nofollow" title="Details &amp; Buchen">
<span class="searchresult-item__daterange"> <span class="searchresult-item__daterange">
<i class="fa fa-arrow-circle-right"></i> {item.dates -> f:count()} {f:if(condition: '{item.dates -> f:count()} > 1', then: 'Termine', else: 'Termin')} <i class="fa fa-arrow-circle-right"></i> {item.dates -> f:count()} {f:if(condition: '{item.dates -> f:count()} > 1', then: 'Termine', else: 'Termin')}
</span> </span>
</a> </f:link.action>
</f:else> </f:else>
</f:if> </f:if>
</f:if> </f:if>
@@ -110,10 +121,14 @@
</div> </div>
<div class="teaserbox__aside__bottom"> <div class="teaserbox__aside__bottom">
<span class="searchresult-item__price">ab {item.minPrice} €</span> <span class="searchresult-item__price">ab {item.minPrice} €</span>
<a href="{item.detailPageUri}{f:if(condition: referringPageUri, then: '?ref={referringPageUri -> f:format.urlencode()}')}" class="button button--full searchresult-item__button" <f:link.action action="detail" controller="Product" extensionName="epproducts" pluginName="product_detail"
rel="nofollow" title="Details"> arguments="{product: item.product.uid, hotel: item.hotel.uid}"
additionalParams="{ref: referrer}"
pageUid="{item.product.detailPage}" absolute="1"
rel="nofollow" title="Details &amp; Buchen"
class="button w-full">
Details{f:if(condition: item.hideBookingButton, else: ' &amp; Buchen')} Details{f:if(condition: item.hideBookingButton, else: ' &amp; Buchen')}
</a> </f:link.action>
</div> </div>
</div> </div>
<a href="#" class="teaserbox-watchlist" x-data="watchlistToggle('{item.key}')" x-bind="trigger"> <a href="#" class="teaserbox-watchlist" x-data="watchlistToggle('{item.key}')" x-bind="trigger">