Merge branch 'release/2020.02-03'

This commit is contained in:
Björn Fromme
2020-02-05 12:42:21 +01:00
15 changed files with 84 additions and 27 deletions
@@ -40,6 +40,11 @@ class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements
*/ */
protected $bookable = true; protected $bookable = true;
/**
* @var bool
*/
protected $hideBookingButton = false;
/** /**
* @var bool * @var bool
*/ */
@@ -302,6 +307,22 @@ class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements
$this->bookable = (bool) $bookable; $this->bookable = (bool) $bookable;
} }
/**
* @return bool
*/
public function getHideBookingButton()
{
return $this->hideBookingButton;
}
/**
* @param bool $hideBookingButton
*/
public function setHideBookingButton($hideBookingButton)
{
$this->hideBookingButton = (bool) $hideBookingButton;
}
/** /**
* @return bool * @return bool
*/ */
@@ -100,7 +100,7 @@ class ProductRepository extends AbstractRepository
'date.board as board', 'date.product_fact as productFact', 'date.hotel_fact as hotelFact', 'date.board as board', 'date.product_fact as productFact', 'date.hotel_fact as hotelFact',
'date.region_fact as regionFact', 'date.services_included as servicesIncluded', 'date.region_fact as regionFact', 'date.services_included as servicesIncluded',
'date.min_price as minPrice', 'date.nights as nights', 'date.available as available', 'date.min_price as minPrice', 'date.nights as nights', 'date.available as available',
'date.pseudo_price as pseudoPrice' 'date.pseudo_price as pseudoPrice', 'date.hide_booking_button as hideBookingButton'
) )
->from('tx_epproducts_domain_model_date', 'date') ->from('tx_epproducts_domain_model_date', 'date')
->leftJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') ->leftJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid')
@@ -154,8 +154,8 @@ class ProductRepository extends AbstractRepository
'date_start as dateStart', 'date_end as dateEnd', 'uid as dateUid', 'min_price as minPrice', 'date_start as dateStart', 'date_end as dateEnd', 'uid as dateUid', 'min_price as minPrice',
'pseudo_price as pseudoPrice', 'nights as nights', 'available as available', 'pseudo_price as pseudoPrice', 'nights as nights', 'available as available',
'skipass_included as skipassIncluded', 'bus_included as busIncluded', 'skipass_included as skipassIncluded', 'bus_included as busIncluded',
'earlybird', 'new', 'daytrip', 'low_contingent as lowContingent', 'earlybird', 'new', 'daytrip', 'hide_booking_button as hideBookingButton',
'concept_code as conceptCode', 'concept_name as conceptName', 'low_contingent as lowContingent', 'concept_code as conceptCode', 'concept_name as conceptName',
'board' 'board'
) )
->from('tx_epproducts_domain_model_date', 'date') ->from('tx_epproducts_domain_model_date', 'date')
@@ -232,7 +232,7 @@ class DateImportService implements SingletonInterface
$dateCount = 0; $dateCount = 0;
$sql = ' $sql = '
SELECT p.uid uid, p.daytrip daytrip, p.important important, p.name product_name, SELECT p.uid uid, p.daytrip daytrip, p.hide_booking_button, p.important important, p.name product_name,
p.searchable product_searchable, p.detail_page product_detail_page, p.keywords product_keywords, p.searchable product_searchable, p.detail_page product_detail_page, p.keywords product_keywords,
p.teaser product_teaser, p.feature product_feature, p.subline product_subline, p.country country, p.teaser product_teaser, p.feature product_feature, p.subline product_subline, p.country country,
p.region region, p.city city, p.path_segment slug, p.region region, p.city city, p.path_segment slug,
@@ -365,6 +365,7 @@ class DateImportService implements SingletonInterface
'bus_pro_id' => $busProId, 'bus_pro_id' => $busProId,
'code' => $code, 'code' => $code,
'daytrip' => $product['daytrip'], 'daytrip' => $product['daytrip'],
'hide_booking_button' => $product['hide_booking_button'],
'important' => $product['important'], 'important' => $product['important'],
]; ];
@@ -619,7 +620,7 @@ class DateImportService implements SingletonInterface
foreach ($section as $service) foreach ($section as $service)
{ {
if ($service['price'] < 0) { if ($service['price'] < 0) {
return $service['price']; return (int) $service['price'];
} }
} }
} }
@@ -724,8 +725,8 @@ class DateImportService implements SingletonInterface
foreach ($services['BUS'] as $service) foreach ($services['BUS'] as $service)
{ {
$date['bus_available'] = 1; $date['bus_available'] = 1;
$date['bus_price'] = $service['price']; $date['bus_price'] = (int) $service['price'];
$date['bus_included'] = ((int) $service['price'] === 0); $date['bus_included'] = (int) ((int) $service['price'] === 0);
} }
} }
@@ -107,6 +107,7 @@ class DateService implements SingletonInterface
'template' => $template, 'template' => $template,
'paCode' => $paCode, 'paCode' => $paCode,
'isDayTrip' => $product->isDaytrip(), 'isDayTrip' => $product->isDaytrip(),
'isHideBookingButton' => $product->getHideBookingButton(),
]); ]);
} }
@@ -148,6 +149,7 @@ class DateService implements SingletonInterface
'template' => $template, 'template' => $template,
'paCode' => $paCode, 'paCode' => $paCode,
'isDayTrip' => $product->isDaytrip(), 'isDayTrip' => $product->isDaytrip(),
'isHideBookingButton' => $product->getHideBookingButton(),
]); ]);
} }
@@ -259,9 +261,10 @@ class DateService implements SingletonInterface
public function preprocessPriceTable(array $options) public function preprocessPriceTable(array $options)
{ {
$resolver = new OptionsResolver(); $resolver = new OptionsResolver();
$resolver->setDefined('paCode'); $resolver->setDefined(['paCode', 'isDayTrip', 'isHideBookingButton']);
$resolver->setRequired(['priceTableData', 'template']); $resolver->setRequired(['priceTableData', 'template']);
$resolver->setDefault('isDayTrip', false); $resolver->setDefault('isDayTrip', false);
$resolver->setDefault('isHideBookingButton', false);
$resolvedOptions = $resolver->resolve($options); $resolvedOptions = $resolver->resolve($options);
$priceTable = []; $priceTable = [];
@@ -269,6 +272,7 @@ class DateService implements SingletonInterface
$template = $resolvedOptions['template']; $template = $resolvedOptions['template'];
$paCode = $resolvedOptions['paCode']; $paCode = $resolvedOptions['paCode'];
$isDayTrip = $resolvedOptions['isDayTrip']; $isDayTrip = $resolvedOptions['isDayTrip'];
$isHideBookingButton = $resolvedOptions['isHideBookingButton'];
foreach ($priceTableData as $row) foreach ($priceTableData as $row)
{ {
@@ -299,6 +303,7 @@ class DateService implements SingletonInterface
'hotelBusProId' => $row['hotelBusProId'], 'hotelBusProId' => $row['hotelBusProId'],
'roomBusProId' => $row['roomBusProId'], 'roomBusProId' => $row['roomBusProId'],
'bookingUrl' => $bookingUrl, 'bookingUrl' => $bookingUrl,
'isHideBookingButton' => $isHideBookingButton,
]; ];
} }
@@ -312,9 +317,10 @@ class DateService implements SingletonInterface
public function preprocessDateTable(array $options) public function preprocessDateTable(array $options)
{ {
$resolver = new OptionsResolver(); $resolver = new OptionsResolver();
$resolver->setDefined('paCode'); $resolver->setDefined(['paCode', 'isDayTrip', 'isHideBookingButton']);
$resolver->setRequired(['dateTableData', 'template']); $resolver->setRequired(['dateTableData', 'template']);
$resolver->setDefault('isDayTrip', false); $resolver->setDefault('isDayTrip', false);
$resolver->setDefault('isHideBookingButton', false);
$resolvedOptions = $resolver->resolve($options); $resolvedOptions = $resolver->resolve($options);
$dateTable = []; $dateTable = [];
@@ -323,6 +329,7 @@ class DateService implements SingletonInterface
$template = $resolvedOptions['template']; $template = $resolvedOptions['template'];
$paCode = $resolvedOptions['paCode']; $paCode = $resolvedOptions['paCode'];
$isDayTrip = $resolvedOptions['isDayTrip']; $isDayTrip = $resolvedOptions['isDayTrip'];
$isHideBookingButton = $resolvedOptions['isHideBookingButton'];
foreach ($dateTableData as $row) foreach ($dateTableData as $row)
{ {
@@ -351,6 +358,7 @@ class DateService implements SingletonInterface
'dateServicesGeneral' => unserialize($row['dateServicesGeneral'], ['allowed_classes' => false]), 'dateServicesGeneral' => unserialize($row['dateServicesGeneral'], ['allowed_classes' => false]),
'available' => $row['available'], 'available' => $row['available'],
'bookingUrl' => $bookingUrl, 'bookingUrl' => $bookingUrl,
'isHideBookingButton' => $isHideBookingButton,
]; ];
} }
@@ -197,6 +197,7 @@ class ProductDataService implements SingletonInterface
'earlybird' => $teaserData['earlybird'], 'earlybird' => $teaserData['earlybird'],
'new' => $teaserData['new'], 'new' => $teaserData['new'],
'daytrip' => $teaserData['daytrip'], 'daytrip' => $teaserData['daytrip'],
'hideBookingButton' => $teaserData['hideBookingButton'],
'lowContingent' => $teaserData['lowContingent'], 'lowContingent' => $teaserData['lowContingent'],
'servicesIncluded' => unserialize($teaserData['servicesIncluded']), 'servicesIncluded' => unserialize($teaserData['servicesIncluded']),
'product' => [ 'product' => [
@@ -107,8 +107,7 @@ class SearchResultService implements SingletonInterface
// Use compound key, hotel is not unique // Use compound key, hotel is not unique
$key = $row['hotelUid'] . ':' . $row['productUid']; $key = $row['hotelUid'] . ':' . $row['productUid'];
$hotelCategory = array_key_exists($row['hotelCategory'], Hotel::$categoryLabels) ? $hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null;
Hotel::$categoryLabels[$row['hotelCategory']] : null;
if (!array_key_exists($key, $items)) { if (!array_key_exists($key, $items)) {
$items[$key] = [ $items[$key] = [
@@ -119,6 +118,7 @@ class SearchResultService implements SingletonInterface
'detailPageUri' => null, 'detailPageUri' => null,
'earlybird' => $row['earlybird'], 'earlybird' => $row['earlybird'],
'daytrip' => $row['daytrip'], 'daytrip' => $row['daytrip'],
'hideBookingButton' => $row['hideBookingButton'],
'lowContingent' => $row['lowContingent'], 'lowContingent' => $row['lowContingent'],
'servicesIncluded' => unserialize($row['servicesIncluded']), 'servicesIncluded' => unserialize($row['servicesIncluded']),
'hotel' => [ 'hotel' => [
@@ -28,7 +28,8 @@ return [
concept_description, board_description, additional_services, programme_description, ski_pass_description, concept_description, board_description, additional_services, programme_description, ski_pass_description,
rating_average, rating_votes_count, teaser_images, header_images, images, concept, country, region, city, rating_average, rating_votes_count, teaser_images, header_images, images, concept, country, region, city,
journey, facts, officetip, hotels, calendar_hotel, faqs,header_title,header_subtitle, alt_product, alt_label, journey, facts, officetip, hotels, calendar_hotel, faqs,header_title,header_subtitle, alt_product, alt_label,
video, banner, additional_regions, badge, daytrip, testimonials, external_link, path_segment', video, banner, additional_regions, badge, daytrip, testimonials, external_link, path_segment,
hide_booking_button',
], ],
'types' => [ 'types' => [
'1' => [ '1' => [
@@ -44,8 +45,8 @@ return [
], ],
'palettes' => [ 'palettes' => [
'destinations' => ['showitem' => 'country,region,city'], 'destinations' => ['showitem' => 'country,region,city'],
'top' => ['showitem' => 'searchable, bookable, daytrip, important, skipass_included, detail_page, 'top' => ['showitem' => 'searchable, bookable, hide_booking_button, daytrip, important, skipass_included,
external_link, path_segment'], detail_page, external_link, path_segment'],
'name' => ['showitem' => 'name,code'], 'name' => ['showitem' => 'name,code'],
'alternative' => ['showitem' => 'alt_product, alt_label'], 'alternative' => ['showitem' => 'alt_product, alt_label'],
'header' => ['showitem' => 'headline,subline,--linebreak--,header_title,header_subtitle'], 'header' => ['showitem' => 'headline,subline,--linebreak--,header_title,header_subtitle'],
@@ -155,6 +156,13 @@ return [
'type' => 'check', 'type' => 'check',
], ],
], ],
'hide_booking_button' => [
'exclude' => 0,
'label' => 'Buchungsbutton verstecken',
'config' => [
'type' => 'check',
],
],
'daytrip' => [ 'daytrip' => [
'exclude' => 0, 'exclude' => 0,
'label' => 'tageweise buchbar', 'label' => 'tageweise buchbar',
@@ -10,6 +10,7 @@ CREATE TABLE tx_epproducts_domain_model_product
searchable tinyint(4) unsigned DEFAULT '1' NOT NULL, searchable tinyint(4) unsigned DEFAULT '1' NOT NULL,
bookable tinyint(4) unsigned DEFAULT '1' NOT NULL, bookable tinyint(4) unsigned DEFAULT '1' NOT NULL,
hide_booking_button tinyint(4) unsigned DEFAULT '0' NOT NULL,
daytrip tinyint(4) unsigned DEFAULT '0' NOT NULL, daytrip tinyint(4) unsigned DEFAULT '0' NOT NULL,
important tinyint(4) unsigned DEFAULT '0' NOT NULL, important tinyint(4) unsigned DEFAULT '0' NOT NULL,
skipass_included tinyint(4) unsigned DEFAULT '1' NOT NULL, skipass_included tinyint(4) unsigned DEFAULT '1' NOT NULL,
@@ -119,6 +120,7 @@ CREATE TABLE tx_epproducts_domain_model_date
available int(11) DEFAULT '0' NOT NULL, available int(11) DEFAULT '0' NOT NULL,
product int(11) unsigned DEFAULT '0', product int(11) unsigned DEFAULT '0',
daytrip tinyint(1) unsigned DEFAULT '0' NOT NULL, daytrip tinyint(1) unsigned DEFAULT '0' NOT NULL,
hide_booking_button tinyint(1) unsigned DEFAULT '0' NOT NULL,
important tinyint(1) unsigned DEFAULT '0' NOT NULL, important tinyint(1) unsigned DEFAULT '0' NOT NULL,
product_name varchar(255) DEFAULT '' NOT NULL, product_name varchar(255) DEFAULT '' NOT NULL,
product_subline varchar(255) DEFAULT '' NOT NULL, product_subline varchar(255) DEFAULT '' NOT NULL,
@@ -36,7 +36,7 @@
<tbody> <tbody>
<tr v-for="(row, index) of datesRows" :class="{ 'hidden': index > 4 && !showAll }"> <tr v-for="(row, index) of datesRows" :class="{ 'hidden': index > 4 && !showAll }">
<td class="dates-table__cell dates-table__cell--date"> <td class="dates-table__cell dates-table__cell--date">
<template v-if="bookable && row.available"> <template v-if="bookable && row.available && !row.isHideBookingButton">
<a class="dates-table__date" href="#" <a class="dates-table__date" href="#"
@click.prevent="loadPriceTable(row.dateUid)"> @click.prevent="loadPriceTable(row.dateUid)">
{{ row.dateStart }} - {{ row.dateEnd }} {{ row.dateStart }} - {{ row.dateEnd }}
@@ -71,9 +71,9 @@
class="hidden-sm hidden-md hidden-lg"><i class="fa fa-info-circle"></i></a> class="hidden-sm hidden-md hidden-lg"><i class="fa fa-info-circle"></i></a>
</td> </td>
<td class="dates-table__cell dates-table__cell--buttons hidden-xs"> <td class="dates-table__cell dates-table__cell--buttons hidden-xs">
<button v-if="row.available" class="button button--small" @click="loadPriceTable(row.dateUid)">Details</button> <button v-if="row.available || row.isHideBookingButton" class="button button--small" @click="loadPriceTable(row.dateUid)">Details</button>
<a :href="row.bookingUrl" target="_blank" v-if="row.available" class="button button--small button--action">Buchen</a> <a :href="row.bookingUrl" target="_blank" v-if="row.available && !row.isHideBookingButton" class="button button--small button--action">Buchen</a>
<span v-if="!row.available" class="label label-default">ausgebucht</span> <span v-if="!row.available && !row.isHideBookingButton" class="label label-default">ausgebucht</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -83,7 +83,7 @@
<div class="col-xs-12 col-md-6"> <div class="col-xs-12 col-md-6">
<a href="#" v-if="datesRows.length > 5" @click.prevent="toggleAllDates()"> <a href="#" v-if="datesRows.length > 5" @click.prevent="toggleAllDates()">
<i class="fa" :class="[{ 'fa-minus-circle': showAll }, { 'fa-plus-circle': !showAll }]"></i> <i class="fa" :class="[{ 'fa-minus-circle': showAll }, { 'fa-plus-circle': !showAll }]"></i>
Alle Termine <template v-if="showAll">aus</template><template v-else>ein</template>blenden <template v-if="showAll">Weniger Termine anzeigen</template><template v-else>Alle Termine anzeigen</template>
</a> </a>
</div> </div>
<div class="col-xs-12 col-md-6"> <div class="col-xs-12 col-md-6">
@@ -38,7 +38,7 @@
<td style="white-space: normal"> <td style="white-space: normal">
<a class="pricetable__room-label" <a class="pricetable__room-label"
:href="row.bookingUrl" target="_blank">{{ row.roomName }}</a> :href="row.bookingUrl" target="_blank">{{ row.roomName }}</a>
<div class="hidden-lg" v-if="row.roomAvailable"> <div class="hidden-lg" v-if="row.roomAvailable && !row.isHideBookingButton">
<a class="button button--small button--action" <a class="button button--small button--action"
:href="row.bookingUrl" target="_blank">Jetzt buchen</a> :href="row.bookingUrl" target="_blank">Jetzt buchen</a>
</div> </div>
@@ -46,7 +46,7 @@
<span class="label label-default">ausgebucht</span> <span class="label label-default">ausgebucht</span>
</div> </div>
</td> </td>
<td style="white-space: nowrap" :style="{'text-decoration': !row.roomAvailable ? 'line-through' : ''}">{{ calculatePrice(row) }} </td> <td style="white-space: nowrap" :style="{'text-decoration': !row.roomAvailable && !row.isHideBookingButton ? 'line-through' : ''}">{{ calculatePrice(row) }} </td>
<td> <td>
<i v-if="showBusIcon(row)" data-placement="top" title="Busfahrt inklusive" class="fa fa-bus tooltiptoggle"></i> <i v-if="showBusIcon(row)" data-placement="top" title="Busfahrt inklusive" class="fa fa-bus tooltiptoggle"></i>
<i v-if="row.dateSkipassIncluded" data-placement="top" title="Skipass inklusive" class="fa fa-tag tooltiptoggle"></i> <i v-if="row.dateSkipassIncluded" data-placement="top" title="Skipass inklusive" class="fa fa-tag tooltiptoggle"></i>
@@ -75,9 +75,9 @@
</div> </div>
</td> </td>
<td class="hidden-xs hidden-md"> <td class="hidden-xs hidden-md">
<a class="button button--small button--action" v-if="row.roomAvailable" <a class="button button--small button--action" v-if="row.roomAvailable && !row.isHideBookingButton"
:href="row.bookingUrl" target="_blank">Buchen</a> :href="row.bookingUrl" target="_blank">Buchen</a>
<span v-if="!row.roomAvailable" class="label label-default">ausgebucht</span> <span v-if="!row.roomAvailable && !row.isHideBookingButton" class="label label-default">ausgebucht</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -56,8 +56,8 @@
<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="url" class="button button--full searchresult-item__button" <a :href="url" class="button button--full searchresult-item__button"
rel="nofollow" title="Details &amp; Buchen"> rel="nofollow" title="Details">
Details &amp; Buchen Details<template v-if="!item.hideBookingButton"> &amp; Buchen</template>
</a> </a>
</div> </div>
</div> </div>
@@ -41,6 +41,9 @@
<trans-unit id="tx_eptheme.label.teaserbutton.product"> <trans-unit id="tx_eptheme.label.teaserbutton.product">
<target>Details &amp; Buchen</target> <target>Details &amp; Buchen</target>
</trans-unit> </trans-unit>
<trans-unit id="tx_eptheme.label.teaserbutton.product_no_booking">
<target>Details</target>
</trans-unit>
<trans-unit id="tx_eptheme_domain_model_lineup"> <trans-unit id="tx_eptheme_domain_model_lineup">
<target>Lineup</target> <target>Lineup</target>
</trans-unit> </trans-unit>
@@ -40,6 +40,9 @@
<trans-unit id="tx_eptheme.label.teaserbutton.product"> <trans-unit id="tx_eptheme.label.teaserbutton.product">
<source>Details &amp; Buchen</source> <source>Details &amp; Buchen</source>
</trans-unit> </trans-unit>
<trans-unit id="tx_eptheme.label.teaserbutton.product_no_booking">
<source>Details</source>
</trans-unit>
<trans-unit id="tx_eptheme_domain_model_lineup"> <trans-unit id="tx_eptheme_domain_model_lineup">
<source>Lineup</source> <source>Lineup</source>
</trans-unit> </trans-unit>
@@ -86,7 +86,12 @@
<div class="teaserbox__back"> <div class="teaserbox__back">
{teaser.product.teaser -> f:format.html()} {teaser.product.teaser -> f:format.html()}
</div> </div>
<button class="button button--full teaserbox__button" title="{teaser.product.name}">{f:translate(key: 'tx_eptheme.label.teaserbutton.product', extensionName: 'ep_theme')}</button> <button class="button button--full teaserbox__button" title="{teaser.product.name}">
<f:if condition="{teaser.hideBookingButton}">
<f:then>{f:translate(key: 'tx_eptheme.label.teaserbutton.product', extensionName: 'ep_theme')}</f:then>
<f:else>{f:translate(key: 'tx_eptheme.label.teaserbutton.product_no_booking', extensionName: 'ep_theme')}</f:else>
</f:if>
</button>
</div> </div>
</f:section> </f:section>
@@ -94,7 +94,12 @@
</div> </div>
<div class="teaserbox__aside__bottom"> <div class="teaserbox__aside__bottom">
<span class="searchresult-item__price">ab {teaser.minPrice} €{f:if(condition: teaser.pseudoPrice, then: ' <s class="searchresult-item__pseudo-price">{teaser.pseudoPrice} €</s>')}</span> <span class="searchresult-item__price">ab {teaser.minPrice} €{f:if(condition: teaser.pseudoPrice, then: ' <s class="searchresult-item__pseudo-price">{teaser.pseudoPrice} €</s>')}</span>
<a href="{teaserUrl}" title="{teaser.product.name}" class="button button--full searchresult-item__button">Details &amp; Buchen</a> <a href="{teaserUrl}" title="{teaser.product.name}" class="button button--full searchresult-item__button">
<f:if condition="{teaser.hideBookingButton}">
<f:then>{f:translate(key: 'tx_eptheme.label.teaserbutton.product', extensionName: 'ep_theme')}</f:then>
<f:else>{f:translate(key: 'tx_eptheme.label.teaserbutton.product_no_booking', extensionName: 'ep_theme')}</f:else>
</f:if>
</a>
</div> </div>
</div> </div>
<watchlist-toggle uid="{teaser.key}" class="teaserbox-watchlist"></watchlist-toggle> <watchlist-toggle uid="{teaser.key}" class="teaserbox-watchlist"></watchlist-toggle>