feat: consolidate event search teasers
addresses #869e4n8py
This commit is contained in:
@@ -171,9 +171,9 @@ class FilterService implements SingletonInterface
|
|||||||
$dateMin = $item['minDateStart'];
|
$dateMin = $item['minDateStart'];
|
||||||
}
|
}
|
||||||
if (null === $dateMax || $item['maxDateEnd'] > $dateMax) {
|
if (null === $dateMax || $item['maxDateEnd'] > $dateMax) {
|
||||||
$dateMax = $row['itemMaxDateEnd'];
|
$dateMax = $item['maxDateEnd'];
|
||||||
}
|
}
|
||||||
if ((bool) $row['busAvailable'] === true) {
|
if ((bool) $item['busAvailable'] === true) {
|
||||||
$busAvailable = true;
|
$busAvailable = true;
|
||||||
}
|
}
|
||||||
foreach ($item['nights'] as $nightsOption) {
|
foreach ($item['nights'] as $nightsOption) {
|
||||||
@@ -182,36 +182,47 @@ class FilterService implements SingletonInterface
|
|||||||
$nights[] = $duration;
|
$nights[] = $duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!array_key_exists($item['country']['uid'], $destinations)) {
|
$itemDestinations = $item['filterMetadata']['destinations'] ?? [[
|
||||||
$destinations[$item['country']['uid']] = [
|
'country' => $item['country'],
|
||||||
|
'region' => $item['region'],
|
||||||
|
]];
|
||||||
|
foreach ($itemDestinations as $itemDestination) {
|
||||||
|
$country = $itemDestination['country'];
|
||||||
|
$region = $itemDestination['region'];
|
||||||
|
if (!array_key_exists($country['uid'], $destinations)) {
|
||||||
|
$destinations[$country['uid']] = [
|
||||||
'country' => [
|
'country' => [
|
||||||
'uid' => $item['country']['uid'],
|
'uid' => $country['uid'],
|
||||||
'label' => $item['country']['name'],
|
'label' => $country['name'],
|
||||||
'code' => $item['country']['code'],
|
'code' => $country['code'],
|
||||||
],
|
],
|
||||||
'regions' => [],
|
'regions' => [],
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
if (!array_key_exists($region['uid'], $destinations[$country['uid']]['regions'])) {
|
||||||
|
$destinations[$country['uid']]['regions'][$region['uid']] = [
|
||||||
|
'uid' => $region['uid'],
|
||||||
|
'label' => $region['name'],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!array_key_exists($item['region']['uid'], $destinations[$item['country']['uid']]['regions'])) {
|
foreach ($item['filterMetadata']['hotelTypes'] ?? [$item['hotel']['type']] as $hotelType) {
|
||||||
$destinations[$item['country']['uid']]['regions'][$item['region']['uid']] = [
|
if (!\in_array($hotelType, $hotelTypes, false)) {
|
||||||
'uid' => $item['region']['uid'],
|
$hotelTypes[] = $hotelType;
|
||||||
'label' => $item['region']['name'],
|
}
|
||||||
];
|
|
||||||
}
|
|
||||||
if (!\in_array($item['hotel']['type'], $hotelTypes, false)) {
|
|
||||||
$hotelTypes[] = $item['hotel']['type'];
|
|
||||||
}
|
}
|
||||||
foreach ($item['rooms'] as $roomType) {
|
foreach ($item['rooms'] as $roomType) {
|
||||||
if (!\in_array($roomType, $roomTypes, false)) {
|
if (!\in_array($roomType, $roomTypes, false)) {
|
||||||
$roomTypes[] = $roomType;
|
$roomTypes[] = $roomType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$boardType = [
|
foreach ($item['filterMetadata']['boardTypes'] ?? [[
|
||||||
'uid' => $item['boardUid'],
|
'uid' => $item['boardUid'],
|
||||||
'label' => $item['board'],
|
'label' => $item['board'],
|
||||||
];
|
]] as $boardType) {
|
||||||
if (!\in_array($boardType, $boardTypes, false)) {
|
if (!\in_array($boardType, $boardTypes, false)) {
|
||||||
$boardTypes[] = $boardType;
|
$boardTypes[] = $boardType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach (FilterOptions::$priceRangeOptions as $index => $priceRange) {
|
foreach (FilterOptions::$priceRangeOptions as $index => $priceRange) {
|
||||||
if (array_key_exists($index, $priceRanges)) {
|
if (array_key_exists($index, $priceRanges)) {
|
||||||
|
|||||||
@@ -49,14 +49,25 @@ class SearchResultService implements SingletonInterface
|
|||||||
*/
|
*/
|
||||||
protected $hotelImageService;
|
protected $hotelImageService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ProductImageService
|
||||||
|
*/
|
||||||
|
protected $productImageService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ProductRepository $productRepository
|
* @param ProductRepository $productRepository
|
||||||
* @param HotelImageService $hotelImageService
|
* @param HotelImageService $hotelImageService
|
||||||
|
* @param ProductImageService $productImageService
|
||||||
*/
|
*/
|
||||||
public function __construct(ProductRepository $productRepository, HotelImageService $hotelImageService)
|
public function __construct(
|
||||||
|
ProductRepository $productRepository,
|
||||||
|
HotelImageService $hotelImageService,
|
||||||
|
ProductImageService $productImageService
|
||||||
|
)
|
||||||
{
|
{
|
||||||
$this->productRepository = $productRepository;
|
$this->productRepository = $productRepository;
|
||||||
$this->hotelImageService = $hotelImageService;
|
$this->hotelImageService = $hotelImageService;
|
||||||
|
$this->productImageService = $productImageService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNormalizedSearchParams(array $searchParams = []): array
|
public function getNormalizedSearchParams(array $searchParams = []): array
|
||||||
@@ -80,10 +91,10 @@ class SearchResultService implements SingletonInterface
|
|||||||
{
|
{
|
||||||
$searchResultData = $this->productRepository->getSearchResult($filterSettings, $excludedConceptUids);
|
$searchResultData = $this->productRepository->getSearchResult($filterSettings, $excludedConceptUids);
|
||||||
|
|
||||||
return $this->preprocess($searchResultData);
|
return $this->preprocess($searchResultData, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function preprocess(array $searchResultData): SearchResult
|
public function preprocess(array $searchResultData, bool $aggregateEvents = false): SearchResult
|
||||||
{
|
{
|
||||||
$data = [];
|
$data = [];
|
||||||
$total = 0;
|
$total = 0;
|
||||||
@@ -105,8 +116,13 @@ class SearchResultService implements SingletonInterface
|
|||||||
|
|
||||||
$items =& $data[$row['conceptUid']]['items'];
|
$items =& $data[$row['conceptUid']]['items'];
|
||||||
|
|
||||||
// Use compound key, hotel is not unique
|
$isAggregatedEvent = $aggregateEvents && 'evt' === $row['conceptCode'];
|
||||||
$key = $row['hotelUid'] . ':' . $row['productUid'];
|
|
||||||
|
// Events are product-level search results. Other results (including
|
||||||
|
// watchlist items) remain tied to a particular hotel.
|
||||||
|
$key = $isAggregatedEvent
|
||||||
|
? (string) $row['productUid']
|
||||||
|
: $row['hotelUid'] . ':' . $row['productUid'];
|
||||||
|
|
||||||
$hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null;
|
$hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null;
|
||||||
|
|
||||||
@@ -122,6 +138,7 @@ class SearchResultService implements SingletonInterface
|
|||||||
'season' => $row['season'],
|
'season' => $row['season'],
|
||||||
'hideBookingButton' => $row['hideBookingButton'],
|
'hideBookingButton' => $row['hideBookingButton'],
|
||||||
'busAvailable' => $row['busAvailable'],
|
'busAvailable' => $row['busAvailable'],
|
||||||
|
'available' => $row['available'],
|
||||||
'lowContingent' => $row['lowContingent'],
|
'lowContingent' => $row['lowContingent'],
|
||||||
'servicesIncluded' => unserialize($row['servicesIncluded'], ['allowed_classes' => false]),
|
'servicesIncluded' => unserialize($row['servicesIncluded'], ['allowed_classes' => false]),
|
||||||
'hotel' => [
|
'hotel' => [
|
||||||
@@ -151,21 +168,55 @@ class SearchResultService implements SingletonInterface
|
|||||||
'subline' => $row['productSubline'],
|
'subline' => $row['productSubline'],
|
||||||
'feature' => $row['productFeature'],
|
'feature' => $row['productFeature'],
|
||||||
'fact' => $row['productFact'],
|
'fact' => $row['productFact'],
|
||||||
|
'images' => $isAggregatedEvent
|
||||||
|
? $this->productImageService->getImages($row['productUid'])
|
||||||
|
: [],
|
||||||
],
|
],
|
||||||
'board' => $row['board'],
|
'board' => $row['board'],
|
||||||
'boardUid' => $row['boardUid'],
|
'boardUid' => $row['boardUid'],
|
||||||
'dates' => [],
|
'dates' => [],
|
||||||
'nights' => [],
|
'nights' => [],
|
||||||
|
'teaserNights' => [],
|
||||||
'rooms' => [],
|
'rooms' => [],
|
||||||
'new' => $row['new'],
|
'new' => $row['new'],
|
||||||
'skiPassIncluded' => $row['skiPassIncluded'],
|
'skiPassIncluded' => $row['skiPassIncluded'],
|
||||||
|
'filterMetadata' => [
|
||||||
|
'destinations' => [],
|
||||||
|
'hotelTypes' => [],
|
||||||
|
'boardTypes' => [],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
$total++;
|
$total++;
|
||||||
}
|
}
|
||||||
|
|
||||||
$item =& $items[$key];
|
$item =& $items[$key];
|
||||||
|
|
||||||
if (!\in_array($row['roomType'], $item['rooms'], false)) {
|
$destination = [
|
||||||
|
'country' => [
|
||||||
|
'uid' => $row['countryUid'],
|
||||||
|
'code' => $row['countryCode'],
|
||||||
|
'name' => $row['countryName'],
|
||||||
|
],
|
||||||
|
'region' => [
|
||||||
|
'uid' => $row['regionUid'],
|
||||||
|
'name' => $row['regionName'],
|
||||||
|
'fact' => $row['regionFact'],
|
||||||
|
'feature' => $row['regionFeature'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
if (!\in_array($destination, $item['filterMetadata']['destinations'], false)) {
|
||||||
|
$item['filterMetadata']['destinations'][] = $destination;
|
||||||
|
}
|
||||||
|
$hotelType = $row['hotelType'] ?? $item['hotel']['type'];
|
||||||
|
if (!\in_array($hotelType, $item['filterMetadata']['hotelTypes'], false)) {
|
||||||
|
$item['filterMetadata']['hotelTypes'][] = $hotelType;
|
||||||
|
}
|
||||||
|
$boardType = ['uid' => $row['boardUid'] ?? null, 'label' => $row['board']];
|
||||||
|
if (!\in_array($boardType, $item['filterMetadata']['boardTypes'], false)) {
|
||||||
|
$item['filterMetadata']['boardTypes'][] = $boardType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($row['roomType']) && !\in_array($row['roomType'], $item['rooms'], false)) {
|
||||||
$item['rooms'][] = $row['roomType'];
|
$item['rooms'][] = $row['roomType'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +244,25 @@ class SearchResultService implements SingletonInterface
|
|||||||
if ($item['minPrice'] === null
|
if ($item['minPrice'] === null
|
||||||
|| $item['minPrice'] > $row['minPrice']) {
|
|| $item['minPrice'] > $row['minPrice']) {
|
||||||
$item['minPrice'] = $row['minPrice'];
|
$item['minPrice'] = $row['minPrice'];
|
||||||
|
// Teaser-specific offer data must describe the cheapest row.
|
||||||
|
$item['teaserNights'] = [$row['nights']];
|
||||||
|
$item['board'] = $row['board'];
|
||||||
|
$item['boardUid'] = $row['boardUid'];
|
||||||
|
$item['daytrip'] = $row['daytrip'];
|
||||||
|
$item['season'] = $row['season'];
|
||||||
|
$item['hideBookingButton'] = $row['hideBookingButton'];
|
||||||
|
$item['servicesIncluded'] = unserialize($row['servicesIncluded'], ['allowed_classes' => false]);
|
||||||
|
$item['new'] = $row['new'];
|
||||||
|
$item['skiPassIncluded'] = $row['skiPassIncluded'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Availability indicators describe the consolidated set, not only
|
||||||
|
// the row that supplies the teaser price.
|
||||||
|
$item['earlybird'] = (bool) $item['earlybird'] || (bool) $row['earlybird'];
|
||||||
|
$item['busAvailable'] = (bool) $item['busAvailable'] || (bool) $row['busAvailable'];
|
||||||
|
$item['lowContingent'] = (bool) $item['lowContingent'] || (bool) $row['lowContingent'];
|
||||||
|
$item['available'] = (bool) $item['available'] || (bool) $row['available'];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort products by minprice within concept sections except for 'event' concept
|
// Sort products by minprice within concept sections except for 'event' concept
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
</f:if>
|
</f:if>
|
||||||
<span class="product-teaser__label-concept">{concept.name}</span>
|
<span class="product-teaser__label-concept">{concept.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<f:if condition="{hideWatchlist}">
|
||||||
|
<f:else>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-controller="watchlist-toggle"
|
data-controller="watchlist-toggle"
|
||||||
data-watchlist-toggle-key-value="{hotel.uid}:{product.uid}"
|
data-watchlist-toggle-key-value="{hotel.uid}:{product.uid}"
|
||||||
@@ -47,6 +49,8 @@
|
|||||||
aria-label="auf die Merkliste setzen">
|
aria-label="auf die Merkliste setzen">
|
||||||
<svg class="product-teaser__watchlist-icon" data-watchlist-toggle-target="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path></svg>
|
<svg class="product-teaser__watchlist-icon" data-watchlist-toggle-target="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path></svg>
|
||||||
</button>
|
</button>
|
||||||
|
</f:else>
|
||||||
|
</f:if>
|
||||||
</div>
|
</div>
|
||||||
<f:if condition="{new}">
|
<f:if condition="{new}">
|
||||||
<div class="absolute top-12 left-2 text-white font-bold text-sm leading-none p-1 bg-badge-gradient">
|
<div class="absolute top-12 left-2 text-white font-bold text-sm leading-none p-1 bg-badge-gradient">
|
||||||
|
|||||||
@@ -64,24 +64,43 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8"
|
<div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8"
|
||||||
data-teasergrid-target="container">
|
data-teasergrid-target="container">
|
||||||
<f:for each="{section.items}" as="item">
|
<f:for each="{section.items}" as="item">
|
||||||
|
<f:if condition="{section.concept.code} == 'evt'">
|
||||||
|
<f:then>
|
||||||
|
<f:variable name="searchProductImage" value="{item.product.images.original.0}"/>
|
||||||
|
<f:variable name="searchForceHotelUid" value="0"/>
|
||||||
|
<f:variable name="searchShowHotelDetails" value="0"/>
|
||||||
|
<f:variable name="searchHideWatchlist" value="1"/>
|
||||||
|
<f:variable name="searchDateFrom" value=""/>
|
||||||
|
<f:variable name="searchDateTo" value=""/>
|
||||||
|
</f:then>
|
||||||
|
<f:else>
|
||||||
|
<f:variable name="searchProductImage" value="{item.hotel.images.original.0}"/>
|
||||||
|
<f:variable name="searchForceHotelUid" value="1"/>
|
||||||
|
<f:variable name="searchShowHotelDetails" value="1"/>
|
||||||
|
<f:variable name="searchHideWatchlist" value="0"/>
|
||||||
|
<f:variable name="searchDateFrom" value="{item.minDateStart}"/>
|
||||||
|
<f:variable name="searchDateTo" value="{item.maxDateEnd}"/>
|
||||||
|
</f:else>
|
||||||
|
</f:if>
|
||||||
<f:render partial="Product/Teaser" section="Teaser" arguments="{
|
<f:render partial="Product/Teaser" section="Teaser" arguments="{
|
||||||
concept: section.concept,
|
concept: section.concept,
|
||||||
hotel: item.hotel,
|
hotel: item.hotel,
|
||||||
product: item.product,
|
product: item.product,
|
||||||
country: item.country,
|
country: item.country,
|
||||||
region: item.region,
|
region: item.region,
|
||||||
productImage: item.hotel.images.original.0,
|
productImage: searchProductImage,
|
||||||
nights: item.nights,
|
nights: item.teaserNights,
|
||||||
board: item.board,
|
board: item.board,
|
||||||
minPrice: item.minPrice,
|
minPrice: item.minPrice,
|
||||||
daytrip: item.daytrip,
|
daytrip: item.daytrip,
|
||||||
season: item.season,
|
season: item.season,
|
||||||
dateFrom: item.minDateStart,
|
dateFrom: searchDateFrom,
|
||||||
dateTo: item.maxDateEnd,
|
dateTo: searchDateTo,
|
||||||
forceHotelUid: 1,
|
forceHotelUid: searchForceHotelUid,
|
||||||
showHotelDetails: 1,
|
showHotelDetails: searchShowHotelDetails,
|
||||||
|
hideWatchlist: searchHideWatchlist,
|
||||||
new: item.new,
|
new: item.new,
|
||||||
skiPassIncluded: item.skiPassIncluded,
|
skiPassIncluded: item.skiPassIncluded,
|
||||||
referringPageUri: referringPageUri
|
referringPageUri: referringPageUri
|
||||||
|
|||||||
Reference in New Issue
Block a user