Feat: Make not-bookable infoboxes editable

This commit is contained in:
Björn Fromme
2023-05-22 21:12:29 +02:00
parent c385e397c6
commit 2627a53495
17 changed files with 894 additions and 447 deletions
Generated
+450 -408
View File
File diff suppressed because it is too large Load Diff
@@ -30,6 +30,7 @@ namespace EP\EpProducts\Controller;
use EP\EpProducts\Domain\Model\Date;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Domain\Repository\InfoboxRepository;
use EP\EpProducts\Domain\Repository\ProductRepository;
use EP\EpProducts\Service\ContingentDataService;
use EP\EpProducts\Service\DateService;
@@ -51,6 +52,11 @@ class AjaxTableController extends ActionController
*/
protected $productRepository;
/**
* @var InfoboxRepository
*/
protected $infoboxRepository;
/**
* @var \EP\EpProducts\Service\FilterService
*/
@@ -73,6 +79,7 @@ class AjaxTableController extends ActionController
/**
* @param ProductRepository $productRepository
* @param InfoboxRepository $infoboxRepository
* @param DateService $dateService
* @param FilterService $filterService
* @param ResellerDataService $resellerDataService
@@ -81,12 +88,14 @@ class AjaxTableController extends ActionController
public function __construct
(
ProductRepository $productRepository,
InfoboxRepository $infoboxRepository,
DateService $dateService,
FilterService $filterService,
ResellerDataService $resellerDataService,
ContingentDataService $contingentDataService
) {
$this->productRepository = $productRepository;
$this->infoboxRepository = $infoboxRepository;
$this->dateService = $dateService;
$this->filterService = $filterService;
$this->resellerDataService = $resellerDataService;
@@ -187,6 +196,8 @@ class AjaxTableController extends ActionController
$isProductWithNoInfoConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(),
$noInfoConceptUids, false);
$nonBookableInfo = $this->getNonBookableInfobox($product);
$this->view->assignMultiple([
'allDates' => $allDates,
'bookableDates' => $bookableDates,
@@ -199,6 +210,7 @@ class AjaxTableController extends ActionController
'hasSurcharge' => $hasSurchargeOrDiscount['surcharge'],
'hasDiscount' => $hasSurchargeOrDiscount['discount'],
'isProductWithNoInfoConcept' => $isProductWithNoInfoConcept,
'nonBookableInfo' => $nonBookableInfo,
]);
}
@@ -242,6 +254,8 @@ class AjaxTableController extends ActionController
$isProductWithNoInfoConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(),
$noInfoConceptUids, false);
$nonBookableInfo = $this->getNonBookableInfobox($product);
$this->view->assignMultiple([
'dateRange' => $dateRange,
'product' => $product,
@@ -253,6 +267,7 @@ class AjaxTableController extends ActionController
'hasDiscount' => $hasSurchargeOrDiscount['discount'],
'isProductWithNoInfoConcept' => $isProductWithNoInfoConcept,
'isSingleDate' => $singleDate,
'nonBookableInfo' => $nonBookableInfo,
]);
}
@@ -267,12 +282,16 @@ class AjaxTableController extends ActionController
} else {
$paCode = null;
}
$nonBookableInfo = $this->getNonBookableInfobox($product);
$pricetable = $this->dateService->getPriceTable([
'product' => $product,
'hotel' => $hotel,
'filterSettings' => $this->filterService->getNormalizedFilterSettings(),
'template' => $this->settings['bpnBookingUrlTemplateCode'],
'paCode' => $paCode,
'nonBookableInfo' => $nonBookableInfo,
]);
$this->view->assign('pricetable', $pricetable);
@@ -297,6 +316,7 @@ class AjaxTableController extends ActionController
$maxPax = max($roomTypesPax);
$template = $this->settings['bpnBookingUrlTemplateCode'];
$pricetable = $this->dateService->getEventPriceTable($product, $maxPax, $template);
$nonBookableInfo = $this->getNonBookableInfobox($product);
$categories = [
Hotel::CATEGORY_STANDARD => 'Standard',
Hotel::CATEGORY_COMFORT => 'Komfort',
@@ -309,6 +329,7 @@ class AjaxTableController extends ActionController
'pricetable' => $pricetable,
'categories' => $categories,
'themekey' => $this->settings['themekey'],
'nonBookableInfo' => $nonBookableInfo,
]);
}
@@ -336,4 +357,16 @@ class AjaxTableController extends ActionController
$this->view->assign('rooms', $rooms);
}
/**
* @param Product $product
* @return \EP\EpProducts\Domain\Model\Infobox
*/
protected function getNonBookableInfobox(Product $product)
{
if (null !== $nonBookableInfo = $product->getNonBookableInfo()) {
return $nonBookableInfo;
}
return $this->infoboxRepository->getDefault();
}
}
@@ -30,6 +30,7 @@ namespace EP\EpProducts\Controller;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Domain\Repository\HotelRepository;
use EP\EpProducts\Domain\Repository\InfoboxRepository;
use EP\EpProducts\Domain\Repository\ProductRepository;
use EP\EpProducts\Service\ContingentDataService;
use EP\EpProducts\Service\DateService;
@@ -76,6 +77,11 @@ class ProductController extends ActionController
*/
private $contingentDataService;
/**
* @var InfoboxRepository
*/
private $infoboxRepository;
/**
* @param ProductRepository $productRepository
* @param ProductDataService $productDataService
@@ -84,6 +90,7 @@ class ProductController extends ActionController
* @param ContingentDataService $contingentDataService
* @param DateService $dateService
* @param FilterService $filterService
* @param InfoboxRepository $infoboxRepository
*/
public function __construct(
ProductRepository $productRepository,
@@ -92,7 +99,8 @@ class ProductController extends ActionController
HotelDataService $hotelDataService,
ContingentDataService $contingentDataService,
DateService $dateService,
FilterService $filterService
FilterService $filterService,
InfoboxRepository $infoboxRepository
) {
$this->productRepository = $productRepository;
$this->productDataService = $productDataService;
@@ -101,6 +109,7 @@ class ProductController extends ActionController
$this->contingentDataService = $contingentDataService;
$this->dateService = $dateService;
$this->filterService = $filterService;
$this->infoboxRepository = $infoboxRepository;
}
/**
@@ -227,12 +236,15 @@ class ProductController extends ActionController
$dateFrom = $this->settings['dateFrom'] ? date('Y-m-d', $this->settings['dateFrom']) : null;
$dateTo = $this->settings['dateTo'] ? date('Y-m-d', $this->settings['dateTo']) : null;
$data = $this->configurationManager->getContentObject()->data;
$nonBookableInfo = $this->settings['infoboxUid'] ? $this->infoboxRepository->findByUid($this->settings['infoboxUid']) : $this->infoboxRepository->findByDefaultText();
$this->view->assign('data', $data);
$this->view->assign('teasers', $teasers);
$this->view->assign('dateAuto', $dateAuto);
$this->view->assign('filterSettings', $filterSettings);
$this->view->assign('dateFrom', $dateFrom);
$this->view->assign('dateTo', $dateTo);
$this->view->assign('nonBookableInfo', $nonBookableInfo);
}
}
@@ -0,0 +1,73 @@
<?php
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Infobox extends AbstractEntity
{
/**
* @var string
*/
protected $title;
/**
* @var string
*/
protected $text;
/**
* @var bool
*/
protected $defaultText;
/**
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* @param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* @return string
*/
public function getText()
{
return $this->text;
}
/**
* @param string $text
*/
public function setText($text)
{
$this->text = $text;
}
/**
* @return bool
*/
public function isDefaultText()
{
return $this->defaultText;
}
/**
* @param bool $defaultText
*/
public function setDefaultText($defaultText)
{
$this->defaultText = $defaultText;
}
}
@@ -280,6 +280,11 @@ class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements
*/
protected $minPrice;
/**
* @var \EP\EpProducts\Domain\Model\Infobox
*/
protected $nonBookableInfo;
public function __construct()
{
$this->initStorageObjects();
@@ -1132,4 +1137,20 @@ class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements
$this->minPrice = $minPrice;
}
/**
* @return Infobox
*/
public function getNonBookableInfo()
{
return $this->nonBookableInfo;
}
/**
* @param Infobox $nonBookableInfo
*/
public function setNonBookableInfo($nonBookableInfo)
{
$this->nonBookableInfo = $nonBookableInfo;
}
}
@@ -0,0 +1,21 @@
<?php
namespace EP\EpProducts\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
class InfoboxRepository extends Repository
{
public function getDefault()
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$result = $query
->matching($query->equals('defaultText', true))
->execute()
;
return $result->getFirst();
}
}
@@ -225,6 +225,26 @@
</config>
</TCEforms>
</settings.hideMoreLink>
<settings.infoboxUid>
<TCEforms>
<label>Infobox nicht buchbar:</label>
<displayCond><![CDATA[FIELD:switchableControllerActions:=:Product->teasergroup]]></displayCond>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<foreign_table>tx_epproducts_domain_model_infobox</foreign_table>
<foreign_table_where>ORDER BY title</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Default</numIndex>
<numIndex index="1">0</numIndex>
</numIndex>
</items>
<minitems>0</minitems>
<maxitems>1</maxitems>
</config>
</TCEforms>
</settings.infoboxUid>
</el>
</ROOT>
</sDEF>
@@ -0,0 +1,143 @@
<?php
return [
'ctrl' => [
'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_infobox',
'default_sortby' => 'ORDER BY title',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'dividers2tabs' => true,
'versioningWS' => true,
'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l10n_parent',
'transOrigDiffSourceField' => 'l10n_diffsource',
'delete' => 'deleted',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'name',
'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg',
],
'types' => [
'1' => [
'showitem' => '--palette--;;top, title, text',
],
],
'palettes' => [
'top' => ['showitem' => 'hidden, default_text'],
],
'columns' => [
'sys_language_uid' => [
'exclude' => 0,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'foreign_table' => 'sys_language',
'foreign_table_where' => 'ORDER BY sys_language.title',
'items' => [
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1],
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0]
],
'default' => 0,
],
],
'l10n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0',
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [
['', 0],
],
'foreign_table' => 'tx_epproducts_domain_model_infobox',
'foreign_table_where' => 'AND tx_epproducts_domain_model_infobox.pid=###CURRENT_PID### AND tx_epproducts_domain_model_infobox.sys_language_uid IN (-1,0)',
],
],
'l10n_diffsource' => [
'config' => [
'type' => 'passthrough',
],
],
't3ver_label' => [
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel',
'config' => [
'type' => 'input',
'size' => 30,
'max' => 255,
]
],
'hidden' => [
'exclude' => 0,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden',
'config' => [
'type' => 'check',
],
],
'starttime' => [
'exclude' => 0,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
'config' => [
'type' => 'input',
'size' => 13,
'eval' => 'datetime',
'checkbox' => 0,
'default' => 0,
'behaviour' => [
'allowLanguageSynchronization' => true,
],
'renderType' => 'inputDateTime',
],
],
'endtime' => [
'exclude' => 0,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
'config' => [
'type' => 'input',
'size' => 13,
'eval' => 'datetime',
'checkbox' => 0,
'default' => 0,
'behaviour' => [
'allowLanguageSynchronization' => true,
],
'renderType' => 'inputDateTime',
],
],
'title' => [
'exclude' => 0,
'label' => 'Titel (nur intern)',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim,required'
],
],
'text' => [
'exclude' => 0,
'label' => 'Text',
'config' => [
'type' => 'text',
'cols' => 40,
'rows' => 15,
'eval' => 'trim,required',
'enableRichtext' => true,
]
],
'default_text' => [
'exclude' => 0,
'label' => 'Default',
'config' => [
'type' => 'check',
],
],
],
];
@@ -27,7 +27,7 @@ return [
'showitem' => '--palette--;;top, name_internal, --palette--;;name, --palette--;;header,
--palette--;;destinations, --palette--;;misc, --palette--;Alternative;alternative,
--div--;Texte, teaser, teaser_long, concept_description, board_description, additional_services,
included_services, programme_description, ski_pass_description,
included_services, programme_description, ski_pass_description, non_bookable_info,
--div--;Verknüpfungen, --palette--;;records, hotels, facts, faqs, additional_regions,
--div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images, banner, video,
--div--;Bewertung, --palette--;;rating, testimonials,
@@ -817,6 +817,21 @@ return [
'maxitems' => 1,
],
],
'non_bookable_info' => [
'exclude' => 0,
'label' => 'Infobox nicht buchbar',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'foreign_table' => 'tx_epproducts_domain_model_infobox',
'foreign_table_where' => 'AND tx_epproducts_domain_model_infobox.deleted = 0 ORDER BY tx_epproducts_domain_model_infobox.title',
'items' => [
['Standardtext', 0],
],
'minitems' => 0,
'maxitems' => 1,
],
],
'external_link' => [
'exclude' => false,
'label' => 'Externer Link',
@@ -63,6 +63,9 @@
<trans-unit id="tx_epproducts_domain_model_reseller">
<source>Vertriebspartner</source>
</trans-unit>
<trans-unit id="tx_epproducts_domain_model_infobox">
<source>Infobox</source>
</trans-unit>
</body>
</file>
</xliff>
@@ -61,6 +61,7 @@ CREATE TABLE tx_epproducts_domain_model_product
video varchar(255) DEFAULT '' NOT NULL,
banner int(11) unsigned NOT NULL default '0',
badge int(11) unsigned NOT NULL default '0',
non_bookable_info int(11) unsigned NOT NULL default '0',
non_bookable_dates varchar(255) DEFAULT '' NOT NULL,
min_price int(11) unsigned NOT NULL default '0',
tstamp int(11) unsigned DEFAULT '0' NOT NULL,
@@ -1304,6 +1305,48 @@ CREATE TABLE tx_epproducts_domain_model_reseller
);
#
# Table structure for table 'tx_epproducts_domain_model_infobox'
#
CREATE TABLE tx_epproducts_domain_model_infobox
(
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
title varchar(255) DEFAULT '' NOT NULL,
text text NOT NULL,
default_text tinyint(4) unsigned DEFAULT '0' NOT NULL,
tstamp int(11) unsigned DEFAULT '0' NOT NULL,
crdate int(11) unsigned DEFAULT '0' NOT NULL,
cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
deleted tinyint(4) unsigned DEFAULT '0' NOT NULL,
hidden tinyint(4) unsigned DEFAULT '0' NOT NULL,
starttime int(11) unsigned DEFAULT '0' NOT NULL,
endtime int(11) unsigned DEFAULT '0' NOT NULL,
t3ver_oid int(11) DEFAULT '0' NOT NULL,
t3ver_id int(11) DEFAULT '0' NOT NULL,
t3ver_wsid int(11) DEFAULT '0' NOT NULL,
t3ver_label varchar(255) DEFAULT '' NOT NULL,
t3ver_state tinyint(4) DEFAULT '0' NOT NULL,
t3ver_stage int(11) DEFAULT '0' NOT NULL,
t3ver_count int(11) DEFAULT '0' NOT NULL,
t3ver_tstamp int(11) DEFAULT '0' NOT NULL,
t3ver_move_id int(11) DEFAULT '0' NOT NULL,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
PRIMARY KEY (uid),
KEY parent (pid),
KEY t3ver_oid (t3ver_oid, t3ver_wsid),
KEY language (l10n_parent, sys_language_uid)
);
#
# Table structure for table 'sys_domain'
#
@@ -5,3 +5,7 @@
.anchor {
@apply block h-0 w-0 pt-16 -mt-16;
}
.alert {
@apply bg-ep-primary-bg p-4 text-ep-primary-dark;
}
@@ -5,7 +5,9 @@
<f:if condition="{pricetable -> f:count()} == 0">
<f:then>
<f:render partial="NotBookable" arguments="{_all}"/>
<div class="alert alert-info" data-rte-content>
{nonBookableInfo.text -> f:format.html()}
</div>
</f:then>
<f:else>
<div class="w-full max-w-full overflow-x-scroll md:overflow-x-auto"
@@ -15,7 +15,9 @@
<f:if condition="{isProductWithNoInfoConcept}">
<f:else>
<f:if condition="{unavailable}">
<f:render partial="NotBookable" arguments="{_all}"/>
<div class="alert alert-info" data-rte-content>
{nonBookableInfo.text -> f:format.html()}
</div>
</f:if>
</f:else>
</f:if>
@@ -11,7 +11,9 @@
<f:render section="Table" arguments="{_all}"/>
</f:then>
<f:else>
<f:render partial="NotBookable" arguments="{_all}"/>
<div class="alert alert-info" data-rte-content>
{nonBookableInfo.text -> f:format.html()}
</div>
</f:else>
</f:if>
</f:section>
@@ -15,7 +15,9 @@
<f:if condition="{isProductWithNoInfoConcept}">
<f:else>
<f:if condition="{unavailable}">
<f:render partial="NotBookable" arguments="{_all}"/>
<div class="alert alert-info" data-rte-content>
{nonBookableInfo.text -> f:format.html()}
</div>
</f:if>
</f:else>
</f:if>
@@ -13,27 +13,28 @@
<f:render partial="Header/Header"
arguments="{header: data.header, subheader: data.subheader, layout: data.header_layout, link: data.header_link, default: settings.defaultHeaderType}" />
<f:if condition="{teasers}">
<div class="flex items-center space-x-1 mb-2 sm:hidden">
<button type="button"
class="opacity-50"
data-action="teasergrid#grid"
data-teasergrid-target="button">
<svg class="w-5 h-5 text-ep-primary">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-grid"></use>
</svg>
</button>
<button type="button"
data-action="teasergrid#block"
data-teasergrid-target="button">
<svg class="w-5 h-5 text-ep-primary">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-single"></use>
</svg>
</button>
</div>
<div class="grid grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8"
data-teasergrid-target="container">
<f:for each="{teasers}" as="teaser" iteration="iteration">
<f:render partial="Product/Teaser" section="Teaser" arguments="{
<f:then>
<div class="flex items-center space-x-1 mb-2 sm:hidden">
<button type="button"
class="opacity-50"
data-action="teasergrid#grid"
data-teasergrid-target="button">
<svg class="w-5 h-5 text-ep-primary">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-grid"></use>
</svg>
</button>
<button type="button"
data-action="teasergrid#block"
data-teasergrid-target="button">
<svg class="w-5 h-5 text-ep-primary">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-single"></use>
</svg>
</button>
</div>
<div class="grid grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8"
data-teasergrid-target="container">
<f:for each="{teasers}" as="teaser" iteration="iteration">
<f:render partial="Product/Teaser" section="Teaser" arguments="{
concept: teaser.concept,
hotel: teaser.hotel,
product: teaser.product,
@@ -50,19 +51,27 @@
dateTo: dateTo,
index: iteration.index
}"/>
</f:for>
</div>
<f:if condition="{settings.hideMoreLink}">
<f:else>
<div class="text-right mt-4">
<f:link.typolink class="button bg-button"
parameter="{settings.defaultSearchPageUid}"
additionalParams="f={filterSettings -> ep:filterSettingsEncoder()}">
Alle anzeigen
</f:link.typolink>
</f:for>
</div>
<f:if condition="{settings.hideMoreLink}">
<f:else>
<div class="text-right mt-4">
<f:link.typolink class="button bg-button"
parameter="{settings.defaultSearchPageUid}"
additionalParams="f={filterSettings -> ep:filterSettingsEncoder()}">
Alle anzeigen
</f:link.typolink>
</div>
</f:else>
</f:if>
</f:then>
<f:else>
<f:if condition="{nonBookableInfo}">
<div class="alert alert-info" data-rte-content>
{nonBookableInfo.text -> f:format.html()}
</div>
</f:else>
</f:if>
</f:if>
</f:else>
</f:if>
</div>
</div>