feat: consolidate event search teasers
addresses #869e4n8py
This commit is contained in:
@@ -171,9 +171,9 @@ class FilterService implements SingletonInterface
|
||||
$dateMin = $item['minDateStart'];
|
||||
}
|
||||
if (null === $dateMax || $item['maxDateEnd'] > $dateMax) {
|
||||
$dateMax = $row['itemMaxDateEnd'];
|
||||
$dateMax = $item['maxDateEnd'];
|
||||
}
|
||||
if ((bool) $row['busAvailable'] === true) {
|
||||
if ((bool) $item['busAvailable'] === true) {
|
||||
$busAvailable = true;
|
||||
}
|
||||
foreach ($item['nights'] as $nightsOption) {
|
||||
@@ -182,37 +182,48 @@ class FilterService implements SingletonInterface
|
||||
$nights[] = $duration;
|
||||
}
|
||||
}
|
||||
if (!array_key_exists($item['country']['uid'], $destinations)) {
|
||||
$destinations[$item['country']['uid']] = [
|
||||
$itemDestinations = $item['filterMetadata']['destinations'] ?? [[
|
||||
'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' => [
|
||||
'uid' => $item['country']['uid'],
|
||||
'label' => $item['country']['name'],
|
||||
'code' => $item['country']['code'],
|
||||
'uid' => $country['uid'],
|
||||
'label' => $country['name'],
|
||||
'code' => $country['code'],
|
||||
],
|
||||
'regions' => [],
|
||||
];
|
||||
}
|
||||
if (!array_key_exists($item['region']['uid'], $destinations[$item['country']['uid']]['regions'])) {
|
||||
$destinations[$item['country']['uid']]['regions'][$item['region']['uid']] = [
|
||||
'uid' => $item['region']['uid'],
|
||||
'label' => $item['region']['name'],
|
||||
if (!array_key_exists($region['uid'], $destinations[$country['uid']]['regions'])) {
|
||||
$destinations[$country['uid']]['regions'][$region['uid']] = [
|
||||
'uid' => $region['uid'],
|
||||
'label' => $region['name'],
|
||||
];
|
||||
}
|
||||
if (!\in_array($item['hotel']['type'], $hotelTypes, false)) {
|
||||
$hotelTypes[] = $item['hotel']['type'];
|
||||
}
|
||||
foreach ($item['filterMetadata']['hotelTypes'] ?? [$item['hotel']['type']] as $hotelType) {
|
||||
if (!\in_array($hotelType, $hotelTypes, false)) {
|
||||
$hotelTypes[] = $hotelType;
|
||||
}
|
||||
}
|
||||
foreach ($item['rooms'] as $roomType) {
|
||||
if (!\in_array($roomType, $roomTypes, false)) {
|
||||
$roomTypes[] = $roomType;
|
||||
}
|
||||
}
|
||||
$boardType = [
|
||||
foreach ($item['filterMetadata']['boardTypes'] ?? [[
|
||||
'uid' => $item['boardUid'],
|
||||
'label' => $item['board'],
|
||||
];
|
||||
]] as $boardType) {
|
||||
if (!\in_array($boardType, $boardTypes, false)) {
|
||||
$boardTypes[] = $boardType;
|
||||
}
|
||||
}
|
||||
foreach (FilterOptions::$priceRangeOptions as $index => $priceRange) {
|
||||
if (array_key_exists($index, $priceRanges)) {
|
||||
continue;
|
||||
|
||||
@@ -49,14 +49,25 @@ class SearchResultService implements SingletonInterface
|
||||
*/
|
||||
protected $hotelImageService;
|
||||
|
||||
/**
|
||||
* @var ProductImageService
|
||||
*/
|
||||
protected $productImageService;
|
||||
|
||||
/**
|
||||
* @param ProductRepository $productRepository
|
||||
* @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->hotelImageService = $hotelImageService;
|
||||
$this->productImageService = $productImageService;
|
||||
}
|
||||
|
||||
public function getNormalizedSearchParams(array $searchParams = []): array
|
||||
@@ -80,10 +91,10 @@ class SearchResultService implements SingletonInterface
|
||||
{
|
||||
$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 = [];
|
||||
$total = 0;
|
||||
@@ -105,8 +116,13 @@ class SearchResultService implements SingletonInterface
|
||||
|
||||
$items =& $data[$row['conceptUid']]['items'];
|
||||
|
||||
// Use compound key, hotel is not unique
|
||||
$key = $row['hotelUid'] . ':' . $row['productUid'];
|
||||
$isAggregatedEvent = $aggregateEvents && 'evt' === $row['conceptCode'];
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -122,6 +138,7 @@ class SearchResultService implements SingletonInterface
|
||||
'season' => $row['season'],
|
||||
'hideBookingButton' => $row['hideBookingButton'],
|
||||
'busAvailable' => $row['busAvailable'],
|
||||
'available' => $row['available'],
|
||||
'lowContingent' => $row['lowContingent'],
|
||||
'servicesIncluded' => unserialize($row['servicesIncluded'], ['allowed_classes' => false]),
|
||||
'hotel' => [
|
||||
@@ -151,21 +168,55 @@ class SearchResultService implements SingletonInterface
|
||||
'subline' => $row['productSubline'],
|
||||
'feature' => $row['productFeature'],
|
||||
'fact' => $row['productFact'],
|
||||
'images' => $isAggregatedEvent
|
||||
? $this->productImageService->getImages($row['productUid'])
|
||||
: [],
|
||||
],
|
||||
'board' => $row['board'],
|
||||
'boardUid' => $row['boardUid'],
|
||||
'dates' => [],
|
||||
'nights' => [],
|
||||
'teaserNights' => [],
|
||||
'rooms' => [],
|
||||
'new' => $row['new'],
|
||||
'skiPassIncluded' => $row['skiPassIncluded'],
|
||||
'filterMetadata' => [
|
||||
'destinations' => [],
|
||||
'hotelTypes' => [],
|
||||
'boardTypes' => [],
|
||||
],
|
||||
];
|
||||
$total++;
|
||||
}
|
||||
|
||||
$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'];
|
||||
}
|
||||
|
||||
@@ -193,8 +244,25 @@ class SearchResultService implements SingletonInterface
|
||||
if ($item['minPrice'] === null
|
||||
|| $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
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
</f:if>
|
||||
<span class="product-teaser__label-concept">{concept.name}</span>
|
||||
</div>
|
||||
<f:if condition="{hideWatchlist}">
|
||||
<f:else>
|
||||
<button type="button"
|
||||
data-controller="watchlist-toggle"
|
||||
data-watchlist-toggle-key-value="{hotel.uid}:{product.uid}"
|
||||
@@ -47,6 +49,8 @@
|
||||
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>
|
||||
</button>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</div>
|
||||
<f:if condition="{new}">
|
||||
<div class="absolute top-12 left-2 text-white font-bold text-sm leading-none p-1 bg-badge-gradient">
|
||||
|
||||
@@ -66,22 +66,41 @@
|
||||
<div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8"
|
||||
data-teasergrid-target="container">
|
||||
<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="{
|
||||
concept: section.concept,
|
||||
hotel: item.hotel,
|
||||
product: item.product,
|
||||
country: item.country,
|
||||
region: item.region,
|
||||
productImage: item.hotel.images.original.0,
|
||||
nights: item.nights,
|
||||
productImage: searchProductImage,
|
||||
nights: item.teaserNights,
|
||||
board: item.board,
|
||||
minPrice: item.minPrice,
|
||||
daytrip: item.daytrip,
|
||||
season: item.season,
|
||||
dateFrom: item.minDateStart,
|
||||
dateTo: item.maxDateEnd,
|
||||
forceHotelUid: 1,
|
||||
showHotelDetails: 1,
|
||||
dateFrom: searchDateFrom,
|
||||
dateTo: searchDateTo,
|
||||
forceHotelUid: searchForceHotelUid,
|
||||
showHotelDetails: searchShowHotelDetails,
|
||||
hideWatchlist: searchHideWatchlist,
|
||||
new: item.new,
|
||||
skiPassIncluded: item.skiPassIncluded,
|
||||
referringPageUri: referringPageUri
|
||||
|
||||
Reference in New Issue
Block a user