fix: date tables and group booking links on TYPO3 13
- Uri\ProductViewHelper builds links with the Extbase UriBuilder (no
controller context in v13), also from content elements
- Null-safe reads of missing row fields and plugin settings, with the
values PHP 7.4 produced
- /gruppe/ without a product redirects to the search page again
instead of failing on findByIdentifier(null)
Checked against the TYPO3 10 project: 355 of 359 /gruppe/{product}
pages identical (the rest differ by newer content), all product pages
render, 377 of 396 date tables load (the 19 others fail on TYPO3 10
too: products without a hotel).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
34f2d55f2c
commit
e70c5f48d5
7 files changed
+92
-72
No files matched your search
@@ -126,7 +126,7 @@ class AjaxTableController extends ActionController
|
||||
?string $dateFrom = null,
|
||||
?string $dateTo = null
|
||||
): ResponseInterface {
|
||||
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
|
||||
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'] ?? '', true);
|
||||
$filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids);
|
||||
$filterSettings['dateFrom'] = $dateFrom;
|
||||
$filterSettings['dateTo'] = $dateTo;
|
||||
@@ -184,7 +184,7 @@ class AjaxTableController extends ActionController
|
||||
$hasSurchargeOrDiscount = $this->dateService->checkSurcharcheOrDiscount($allDates);
|
||||
// $hasIncludedServices = $this->dateService->hasIncludedServices($allDates);
|
||||
$hasIncludedServices = true;
|
||||
$noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids']);
|
||||
$noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids'] ?? '');
|
||||
$isProductWithNoInfoConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(),
|
||||
$noInfoConceptUids, false);
|
||||
|
||||
@@ -223,7 +223,7 @@ class AjaxTableController extends ActionController
|
||||
Date $date,
|
||||
bool $singleDate = false
|
||||
): ResponseInterface {
|
||||
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
|
||||
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'] ?? '', true);
|
||||
$filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids);
|
||||
$myEpUrl = $this->myEpUrlResolverService->resolve();
|
||||
|
||||
@@ -242,7 +242,7 @@ class AjaxTableController extends ActionController
|
||||
$dateEnd = $date ? $date->getDateEnd()->format('d.m.y') : '';
|
||||
$dateRange = sprintf('%s - %s', $dateStart, $dateEnd);
|
||||
|
||||
$noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids']);
|
||||
$noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids'] ?? '');
|
||||
$isProductWithNoInfoConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(), $noInfoConceptUids);
|
||||
|
||||
$nonBookableInfo = $this->getNonBookableInfobox($product);
|
||||
@@ -298,7 +298,7 @@ class AjaxTableController extends ActionController
|
||||
*/
|
||||
public function eventPricetableAction(Product $product): ResponseInterface
|
||||
{
|
||||
$roomTypes = GeneralUtility::trimExplode(',', $this->settings['priceTableRoomTypes']);
|
||||
$roomTypes = GeneralUtility::trimExplode(',', $this->settings['priceTableRoomTypes'] ?? '');
|
||||
$roomTypesPax = array_map(function ($type) {
|
||||
return (int) $type;
|
||||
}, $roomTypes);
|
||||
@@ -326,7 +326,7 @@ class AjaxTableController extends ActionController
|
||||
'pricetable' => $priceTable,
|
||||
'hasIncludedServices' => $hasIncludedServices,
|
||||
'categories' => $categories,
|
||||
'themekey' => $this->settings['themekey'],
|
||||
'themekey' => ($this->settings['themekey'] ?? null),
|
||||
'nonBookableInfo' => $nonBookableInfo,
|
||||
'myEpUrl' => $myEpUrl,
|
||||
]);
|
||||
|
||||
@@ -130,8 +130,8 @@ class ProductController extends ActionController
|
||||
): ResponseInterface {
|
||||
// Get product to display from flexform settings if not provided via url
|
||||
if ($product === null) {
|
||||
$productUid = $this->settings['productUid'];
|
||||
$product = $this->productRepository->findByIdentifier($productUid);
|
||||
$productUid = ($this->settings['productUid'] ?? null);
|
||||
$product = $productUid ? $this->productRepository->findByIdentifier($productUid) : null;
|
||||
}
|
||||
// Redirect to search page in case no product can be determined
|
||||
if ($product === null) {
|
||||
@@ -141,12 +141,12 @@ class ProductController extends ActionController
|
||||
'Search',
|
||||
null,
|
||||
['filterSettings' => $filterSettings],
|
||||
$this->settings['defaultSearchPageUid']
|
||||
($this->settings['defaultSearchPageUid'] ?? null)
|
||||
);
|
||||
}
|
||||
// Get hotel to display from flexform settings if not provided via url
|
||||
if ((int)$this->settings['hotelUid'] > 0) {
|
||||
$hotelUid = $this->settings['hotelUid'];
|
||||
if ((int)($this->settings['hotelUid'] ?? null) > 0) {
|
||||
$hotelUid = ($this->settings['hotelUid'] ?? null);
|
||||
$hotel = $this->hotelRepository->findByIdentifier($hotelUid);
|
||||
}
|
||||
if ($hotel === null) {
|
||||
@@ -165,11 +165,11 @@ class ProductController extends ActionController
|
||||
}
|
||||
|
||||
$concept = $product->getConcept();
|
||||
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true);
|
||||
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'] ?? '', true);
|
||||
$isProductWithExcludedConcept = ($concept !== null) && \in_array($concept->getUid(), $excludedConceptUids);
|
||||
$noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids'], true);
|
||||
$noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids'] ?? '', true);
|
||||
$isProductWithNoInfoConcept = ($concept !== null) && \in_array($concept->getUid(), $noInfoConceptUids);
|
||||
$groupInquiryFormConceptUids = GeneralUtility::trimExplode(',', $this->settings['groupInquiryFormConceptUids'], true);
|
||||
$groupInquiryFormConceptUids = GeneralUtility::trimExplode(',', $this->settings['groupInquiryFormConceptUids'] ?? '', true);
|
||||
$isProductWithGroupInquiryForm = ($concept !== null) && \in_array($concept->getUid(), $groupInquiryFormConceptUids);
|
||||
$nonBookableInfo = $product->getNonBookableInfo();
|
||||
|
||||
@@ -198,10 +198,10 @@ class ProductController extends ActionController
|
||||
|
||||
public function pricetableAction(): ResponseInterface
|
||||
{
|
||||
$productUid = $this->settings['productUid'];
|
||||
$productUid = ($this->settings['productUid'] ?? null);
|
||||
/** @var Product $product */
|
||||
$product = $this->productRepository->findByIdentifier($productUid);
|
||||
$hotelUid = $this->settings['hotelUid'];
|
||||
$hotelUid = ($this->settings['hotelUid'] ?? null);
|
||||
/** @var Hotel $hotel */
|
||||
$hotel = $this->hotelRepository->findByIdentifier($hotelUid);
|
||||
$pricetable = $this->dateService->getPriceTable([
|
||||
@@ -230,14 +230,14 @@ class ProductController extends ActionController
|
||||
public function teasergroupAction(): ResponseInterface
|
||||
{
|
||||
$filterSettings = $this->filterService->getFiltersettingsFromSettings($this->settings);
|
||||
$dateAuto = !$this->settings['dateFrom'] && !$this->settings['dateTo'];
|
||||
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true);
|
||||
$limit = $this->settings['hideMoreLink'] ? 0 : 6;
|
||||
$dateAuto = !($this->settings['dateFrom'] ?? null) && !($this->settings['dateTo'] ?? null);
|
||||
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'] ?? '', true);
|
||||
$limit = ($this->settings['hideMoreLink'] ?? null) ? 0 : 6;
|
||||
$teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit);
|
||||
$dateFrom = $this->settings['dateFrom'] ? date('Y-m-d', $this->settings['dateFrom']) : null;
|
||||
$dateTo = $this->settings['dateTo'] ? date('Y-m-d', $this->settings['dateTo']) : null;
|
||||
$dateFrom = ($this->settings['dateFrom'] ?? null) ? date('Y-m-d', ($this->settings['dateFrom'] ?? null)) : null;
|
||||
$dateTo = ($this->settings['dateTo'] ?? null) ? date('Y-m-d', ($this->settings['dateTo'] ?? null)) : null;
|
||||
$data = $this->request->getAttribute('currentContentObject')->data;
|
||||
$nonBookableInfo = $this->settings['infoboxUid'] ? $this->infoboxRepository->findByUid($this->settings['infoboxUid']) : $this->infoboxRepository->getDefault();
|
||||
$nonBookableInfo = ($this->settings['infoboxUid'] ?? null) ? $this->infoboxRepository->findByUid($this->settings['infoboxUid']) : $this->infoboxRepository->getDefault();
|
||||
|
||||
$this->view->assign('data', $data);
|
||||
$this->view->assign('teasers', $teasers);
|
||||
|
||||
@@ -267,7 +267,7 @@ class DateService implements SingletonInterface
|
||||
}
|
||||
if (!array_key_exists($key, $processed)) {
|
||||
$processed[$key] = [
|
||||
'label' => $this->serviceSections[$key],
|
||||
'label' => $this->serviceSections[$key] ?? null,
|
||||
'services' => [],
|
||||
];
|
||||
}
|
||||
@@ -436,7 +436,7 @@ class DateService implements SingletonInterface
|
||||
public function hasIncludedServices(array $tableData): bool
|
||||
{
|
||||
foreach ($tableData as $row) {
|
||||
if ($row['busIncluded'] || $row['skiPassIncluded']) {
|
||||
if (($row['busIncluded'] ?? null) || ($row['skiPassIncluded'] ?? null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -516,10 +516,10 @@ class DateService implements SingletonInterface
|
||||
];
|
||||
|
||||
foreach ($tableData as $row) {
|
||||
if ($row['busPrice'] && $row['busPrice'] > 0) {
|
||||
if (($row['busPrice'] ?? null) && $row['busPrice'] > 0) {
|
||||
$data['surcharge'] = true;
|
||||
}
|
||||
if ($row['discount'] && $row['discount'] < 0) {
|
||||
if (($row['discount'] ?? null) && $row['discount'] < 0) {
|
||||
$data['discount'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,20 +88,20 @@ class HotelDataService implements SingletonInterface
|
||||
return [
|
||||
'minDateStart' => $dateStart,
|
||||
'maxDateEnd' => $dateEnd,
|
||||
'minPrice' => $hotelData['minPrice'],
|
||||
'pseudoPrice' => $hotelData['pseudoPrice'],
|
||||
'available' => $hotelData['available'],
|
||||
'detailPage' => $hotelData['hotelDetailPage'],
|
||||
'name' => $hotelData['hotelName'],
|
||||
'teaser' => $hotelData['hotelTeaser'],
|
||||
'minPrice' => $hotelData['minPrice'] ?? null,
|
||||
'pseudoPrice' => $hotelData['pseudoPrice'] ?? null,
|
||||
'available' => $hotelData['available'] ?? null,
|
||||
'detailPage' => $hotelData['hotelDetailPage'] ?? null,
|
||||
'name' => $hotelData['hotelName'] ?? null,
|
||||
'teaser' => $hotelData['hotelTeaser'] ?? null,
|
||||
'category' => $hotelCategory,
|
||||
'images' => $this->hotelImageService->getImages($hotelUid),
|
||||
'country' => [
|
||||
'name' => $hotelData['country'],
|
||||
'code' => $hotelData['countryCode'],
|
||||
'name' => $hotelData['country'] ?? null,
|
||||
'code' => $hotelData['countryCode'] ?? null,
|
||||
],
|
||||
'region' => [
|
||||
'name' => $hotelData['regionName'],
|
||||
'name' => $hotelData['regionName'] ?? null,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -131,11 +131,11 @@ class ProductDataService implements SingletonInterface
|
||||
$teaser['dates'][] = $row['dateStart'];
|
||||
}
|
||||
|
||||
if (!$teaser['skiPassIncluded'] && $row['skiPassIncluded']) {
|
||||
if (!($teaser['skiPassIncluded'] ?? false) && ($row['skiPassIncluded'] ?? null)) {
|
||||
$teaser['skiPassIncluded'] = true;
|
||||
}
|
||||
|
||||
if (!$teaser['busIncluded'] && $row['busIncluded']) {
|
||||
if (!($teaser['busIncluded'] ?? false) && ($row['busIncluded'] ?? null)) {
|
||||
$teaser['busIncluded'] = true;
|
||||
}
|
||||
|
||||
@@ -182,47 +182,47 @@ class ProductDataService implements SingletonInterface
|
||||
'maxDateEnd' => null,
|
||||
'minPrice' => null,
|
||||
'hotelCount' => 1,
|
||||
'pseudoPrice' => $teaserData['pseudoPrice'],
|
||||
'board' => $teaserData['board'],
|
||||
'earlybird' => $teaserData['earlybird'],
|
||||
'new' => $teaserData['new'],
|
||||
'daytrip' => $teaserData['daytrip'],
|
||||
'hideBookingButton' => $teaserData['hideBookingButton'],
|
||||
'lowContingent' => $teaserData['lowContingent'],
|
||||
'servicesIncluded' => unserialize($teaserData['servicesIncluded'], ['allowed_classes' => false]),
|
||||
'season' => $teaserData['season'],
|
||||
'pseudoPrice' => $teaserData['pseudoPrice'] ?? null,
|
||||
'board' => $teaserData['board'] ?? null,
|
||||
'earlybird' => $teaserData['earlybird'] ?? null,
|
||||
'new' => $teaserData['new'] ?? null,
|
||||
'daytrip' => $teaserData['daytrip'] ?? null,
|
||||
'hideBookingButton' => $teaserData['hideBookingButton'] ?? null,
|
||||
'lowContingent' => $teaserData['lowContingent'] ?? null,
|
||||
'servicesIncluded' => unserialize((string)($teaserData['servicesIncluded'] ?? ''), ['allowed_classes' => false]),
|
||||
'season' => $teaserData['season'] ?? null,
|
||||
'product' => [
|
||||
'uid' => $teaserData['productUid'],
|
||||
'name' => $teaserData['productName'],
|
||||
'subline' => $teaserData['productSubline'],
|
||||
'detailPage' => $teaserData['productDetailPage'],
|
||||
'slug' => $teaserData['productSlug'],
|
||||
'fact' => $teaserData['productFact'],
|
||||
'feature' => $teaserData['productFeature'],
|
||||
'teaser' => $teaserData['productTeaser'],
|
||||
'images' => $this->productImageService->getImages($teaserData['productUid']),
|
||||
'uid' => $teaserData['productUid'] ?? null,
|
||||
'name' => $teaserData['productName'] ?? null,
|
||||
'subline' => $teaserData['productSubline'] ?? null,
|
||||
'detailPage' => $teaserData['productDetailPage'] ?? null,
|
||||
'slug' => $teaserData['productSlug'] ?? null,
|
||||
'fact' => $teaserData['productFact'] ?? null,
|
||||
'feature' => $teaserData['productFeature'] ?? null,
|
||||
'teaser' => $teaserData['productTeaser'] ?? null,
|
||||
'images' => $this->productImageService->getImages($teaserData['productUid'] ?? null),
|
||||
],
|
||||
'concept' => [
|
||||
'uid' => $teaserData['conceptUid'],
|
||||
'name' => $teaserData['conceptName'],
|
||||
'code' => $teaserData['conceptCode'],
|
||||
'popupText' => $teaserData['conceptPopupText'],
|
||||
'uid' => $teaserData['conceptUid'] ?? null,
|
||||
'name' => $teaserData['conceptName'] ?? null,
|
||||
'code' => $teaserData['conceptCode'] ?? null,
|
||||
'popupText' => $teaserData['conceptPopupText'] ?? null,
|
||||
],
|
||||
'hotel' => [
|
||||
'name' => $teaserData['hotelName'],
|
||||
'name' => $teaserData['hotelName'] ?? null,
|
||||
'category' => $hotelCategory,
|
||||
'fact' => $teaserData['hotelFact'],
|
||||
'uid' => $teaserData['hotelUid'],
|
||||
'images' => $this->hotelImageService->getImages($teaserData['hotelUid']),
|
||||
'fact' => $teaserData['hotelFact'] ?? null,
|
||||
'uid' => $teaserData['hotelUid'] ?? null,
|
||||
'images' => $this->hotelImageService->getImages($teaserData['hotelUid'] ?? null),
|
||||
],
|
||||
'country' => [
|
||||
'name' => $teaserData['countryName'],
|
||||
'code' => $teaserData['countryCode'],
|
||||
'name' => $teaserData['countryName'] ?? null,
|
||||
'code' => $teaserData['countryCode'] ?? null,
|
||||
],
|
||||
'region' => [
|
||||
'name' => $teaserData['regionName'],
|
||||
'fact' => $teaserData['regionFact'],
|
||||
'feature' => $teaserData['regionFeature'],
|
||||
'name' => $teaserData['regionName'] ?? null,
|
||||
'fact' => $teaserData['regionFact'] ?? null,
|
||||
'feature' => $teaserData['regionFeature'] ?? null,
|
||||
],
|
||||
'dates' => [],
|
||||
'nights' => [],
|
||||
|
||||
@@ -61,7 +61,7 @@ class ContextProcessor implements DataProcessorInterface
|
||||
if ($regionUid > 0) {
|
||||
$processedData['context']['region'] = $this->getContextRecord($regionUid, 'tx_epproducts_domain_model_region');
|
||||
}
|
||||
if ($data['tx_eptheme_context_pacode']) {
|
||||
if ($data['tx_eptheme_context_pacode'] ?? null) {
|
||||
$processedData['context']['paCode'] = $data['tx_eptheme_context_pacode'];
|
||||
} elseif ($paCode = $this->getPaCodeFromRequest()) {
|
||||
$processedData['context']['paCode'] = $paCode;
|
||||
|
||||
@@ -27,6 +27,12 @@ namespace EP\EpTheme\ViewHelpers\Uri;
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters;
|
||||
use TYPO3\CMS\Extbase\Mvc\Request;
|
||||
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
|
||||
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
|
||||
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
|
||||
|
||||
@@ -73,10 +79,9 @@ class ProductViewHelper extends AbstractViewHelper
|
||||
];
|
||||
}
|
||||
$targetPageUid = is_array($product) ? (int)$product['detailPage'] : $product->getDetailPage();
|
||||
$uri = $this->renderingContext
|
||||
->getControllerContext()
|
||||
->getUriBuilder()
|
||||
$uri = GeneralUtility::makeInstance(UriBuilder::class)
|
||||
->reset()
|
||||
->setRequest($this->getExtbaseRequest())
|
||||
->setTargetPageUid($targetPageUid)
|
||||
->uriFor('detail', $linkArguments, 'Product', 'epproducts', 'product_detail')
|
||||
;
|
||||
@@ -89,4 +94,19 @@ class ProductViewHelper extends AbstractViewHelper
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* The view helper is also used in content elements, which render without an Extbase request
|
||||
*/
|
||||
private function getExtbaseRequest(): RequestInterface
|
||||
{
|
||||
$request = $this->renderingContext->getAttribute(ServerRequestInterface::class);
|
||||
if ($request instanceof RequestInterface) {
|
||||
return $request;
|
||||
}
|
||||
if ($request->getAttribute('extbase') === null) {
|
||||
$request = $request->withAttribute('extbase', new ExtbaseRequestParameters());
|
||||
}
|
||||
return new Request($request);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user