fix: render product detail pages on TYPO3 13
- ObjectManager replaced by GeneralUtility::makeInstance(), the
services fetched that way are public
- QueryBuilder::add('orderBy') and the remaining execute() calls,
detailAction returns a response
- PHP 8 warnings for missing array keys (same values as on PHP 7.4)
- Webcam images are external URLs: plain img tag, f:image only takes
FAL files
- typo3_encore 7 needs the PKG:ssch/typo3-encore: prefix and
external = 1 so TYPO3's path check does not drop the entry
- CDN image rewriting as a middleware instead of EXT:replacer, which
is not PHP 8.5 clean; same pattern, only for page responses
- vhs 8 for the ViewHelpers the templates use
- OpenGraph meta tag manager skips the backend
- myEpUrl with trailing slash as in production
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4eb1de2e29
commit
34f2d55f2c
23 files changed
+222
-57
No files matched your search
@@ -18,6 +18,7 @@
|
||||
"b13/container": "^4.1",
|
||||
"ep/ep-products": "@dev",
|
||||
"ep/ep-theme": "@dev",
|
||||
"fluidtypo3/vhs": "^8.0",
|
||||
"ssch/typo3-encore": "^7.0",
|
||||
"typo3/cms-backend": "^13.4",
|
||||
"typo3/cms-belog": "^13.4",
|
||||
|
||||
Generated
+71
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bb6164470dbc546b9cb0a9b2d31797f2",
|
||||
"content-hash": "9de2907eea881944a362013a40f3cc7e",
|
||||
"packages": [
|
||||
{
|
||||
"name": "b13/container",
|
||||
@@ -976,6 +976,76 @@
|
||||
},
|
||||
"time": "2026-09-28T17:51:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fluidtypo3/vhs",
|
||||
"version": "8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FluidTYPO3/vhs.git",
|
||||
"reference": "cdf53ec693d8a69a8b120d89b01aff2281b0b049"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/FluidTYPO3/vhs/zipball/cdf53ec693d8a69a8b120d89b01aff2281b0b049",
|
||||
"reference": "cdf53ec693d8a69a8b120d89b01aff2281b0b049",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^8.3",
|
||||
"typo3/cms-backend": "^12 || ^13 || ^14.3 || dev-main",
|
||||
"typo3/cms-core": "^12 || ^13 || ^14.3 || dev-main",
|
||||
"typo3/cms-extbase": "^12 || ^13 || ^14.3 || dev-main",
|
||||
"typo3/cms-fluid": "^12 || ^13 || ^14.3 || dev-main",
|
||||
"typo3/cms-frontend": "^12 || ^13 || ^14.3 || dev-main"
|
||||
},
|
||||
"replace": {
|
||||
"typo3-ter/vhs": "self.version"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"phpstan/phpstan": "1.12.1",
|
||||
"phpunit/phpunit": "^5.7 || ^9.5",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-json": "Enable use of v:format.json.encode and v:format.json.decode",
|
||||
"ext-tidy": "Allows to make use of the tidy ViewHelper v:format.tidy",
|
||||
"ext-zlib": "Enable use of v:format.placeholder.lipsum with default lipsum text"
|
||||
},
|
||||
"type": "typo3-cms-extension",
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"extension-key": "vhs"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FluidTYPO3\\Vhs\\": "Classes/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "This is a collection of ViewHelpers for performing rendering tasks that are not natively provided by TYPO3's Fluid templating engine.",
|
||||
"homepage": "https://fluidtypo3.org",
|
||||
"keywords": [
|
||||
"TYPO3 CMS",
|
||||
"fedext",
|
||||
"fluid",
|
||||
"templating",
|
||||
"utility",
|
||||
"viewhelper"
|
||||
],
|
||||
"support": {
|
||||
"chat": "https://typo3.slack.com/archives/C79562JES",
|
||||
"docs": "https://docs.typo3.org/p/fluidtypo3/vhs/main/en-us/",
|
||||
"issues": "https://github.com/FluidTYPO3/vhs/issues",
|
||||
"source": "https://github.com/FluidTYPO3/vhs"
|
||||
},
|
||||
"time": "2026-07-23T10:57:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "8.3.0",
|
||||
|
||||
@@ -54,9 +54,12 @@ return [
|
||||
'myEpApiUrlAuthorize' => '',
|
||||
'myEpApiUrlToken' => '',
|
||||
'myEpStagingUrl' => 'https://my.ep-reisen.net',
|
||||
'myEpUrl' => 'https://my.ep-reisen.de',
|
||||
'myEpUrl' => 'https://my.ep-reisen.de/',
|
||||
'searchPageUid' => '0',
|
||||
],
|
||||
'ep_theme' => [
|
||||
'cdnBaseUrl' => 'https://cdn.ep-reisen.de/',
|
||||
],
|
||||
'extensionmanager' => [
|
||||
'automaticInstallation' => '1',
|
||||
'offlineMode' => '0',
|
||||
|
||||
@@ -65,7 +65,7 @@ class RecordFinder
|
||||
|
||||
$rows = [];
|
||||
|
||||
foreach ($queryBuilder->execute()->fetchAllAssociative() as $row) {
|
||||
foreach ($queryBuilder->executeQuery()->fetchAllAssociative() as $row) {
|
||||
$rows[(int) $row['uid']] = $row;
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ class ProductController extends ActionController
|
||||
?Hotel $hotel = null,
|
||||
$dateFrom = null,
|
||||
$dateTo = null
|
||||
) {
|
||||
): ResponseInterface {
|
||||
// Get product to display from flexform settings if not provided via url
|
||||
if ($product === null) {
|
||||
$productUid = $this->settings['productUid'];
|
||||
@@ -175,7 +175,7 @@ class ProductController extends ActionController
|
||||
|
||||
// Assemble backlink
|
||||
$backlinkUrl = $this->request->getQueryParams()['ref'] ?? null;
|
||||
$backLinkLabel = strpos($backlinkUrl, 'suche') !== -1 ? 'zurück zum Suchergebnis' : 'zurück';
|
||||
$backLinkLabel = strpos((string)$backlinkUrl, 'suche') !== -1 ? 'zurück zum Suchergebnis' : 'zurück';
|
||||
|
||||
$this->view->assignMultiple([
|
||||
'hotel' => $hotel,
|
||||
@@ -193,6 +193,7 @@ class ProductController extends ActionController
|
||||
'nonBookableInfo' => $nonBookableInfo,
|
||||
'pickupsOverview' => $this->pickupOverviewService->getOverview($product, $hotel),
|
||||
]);
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
public function pricetableAction(): ResponseInterface
|
||||
|
||||
@@ -9,6 +9,7 @@ use EP\EpProducts\Service\FilterService;
|
||||
use EP\EpProducts\Traits\DbConnectionTrait;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
|
||||
use TYPO3\CMS\Extbase\Persistence\Repository;
|
||||
|
||||
@@ -18,8 +19,7 @@ abstract class AbstractRepository extends Repository
|
||||
|
||||
public function initializeObject(): void
|
||||
{
|
||||
/** @var $querySettings \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings */
|
||||
$querySettings = $this->objectManager->get(Typo3QuerySettings::class);
|
||||
$querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class);
|
||||
$querySettings->setRespectStoragePage(false);
|
||||
$this->setDefaultQuerySettings($querySettings);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ class HotelRepository extends AbstractRepository
|
||||
->setParameter('productUid', $productUid)
|
||||
;
|
||||
|
||||
return $qb->execute()->fetchAllAssociative();
|
||||
return $qb->executeQuery()->fetchAllAssociative();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +146,7 @@ class HotelRepository extends AbstractRepository
|
||||
;
|
||||
}
|
||||
|
||||
return $qb->execute()->fetchAllAssociative();
|
||||
return $qb->executeQuery()->fetchAllAssociative();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,9 +174,10 @@ class HotelRepository extends AbstractRepository
|
||||
->leftJoin('hotel', 'tx_epproducts_domain_model_country', 'country', 'hotel.country = country.uid')
|
||||
->leftJoin('hotel', 'tx_epproducts_domain_model_region', 'region', 'hotel.region = region.uid')
|
||||
->where('hotel.show_as_teaser = 1')
|
||||
->add('orderBy', 'FIELD(hotel.type, ' . Hotel::TYPE_SPORTS_CLUB . ') DESC')
|
||||
->addOrderBy('hotel.name', 'ASC')
|
||||
;
|
||||
// Raw expression: TYPO3's orderBy() would quote it as an identifier
|
||||
$qb->getConcreteQueryBuilder()->orderBy('FIELD(hotel.type, ' . Hotel::TYPE_SPORTS_CLUB . ') DESC');
|
||||
$qb->addOrderBy('hotel.name', 'ASC');
|
||||
|
||||
if ($excludedHotelUids) {
|
||||
$qb
|
||||
@@ -216,7 +217,7 @@ class HotelRepository extends AbstractRepository
|
||||
;
|
||||
}
|
||||
|
||||
return $qb->execute()->fetchAllAssociative();
|
||||
return $qb->executeQuery()->fetchAllAssociative();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,8 +245,9 @@ class HotelRepository extends AbstractRepository
|
||||
->addGroupBy('date.hotel')
|
||||
->addGroupBy('date.date_start')
|
||||
->addGroupBy('date.nights')
|
||||
->add('orderBy', 'FIELD(date.hotel_type, ' . Hotel::TYPE_SPORTS_CLUB . ') DESC')
|
||||
;
|
||||
// Raw expression: TYPO3's orderBy() would quote it as an identifier
|
||||
$qb->getConcreteQueryBuilder()->orderBy('FIELD(date.hotel_type, ' . Hotel::TYPE_SPORTS_CLUB . ') DESC');
|
||||
|
||||
if ($excludeNonSearchable) {
|
||||
$qb->where('date.product_searchable = 1');
|
||||
|
||||
@@ -104,7 +104,7 @@ class ProductRepository extends AbstractRepository
|
||||
);
|
||||
}
|
||||
|
||||
if ($filterSettings['productsPid']) {
|
||||
if (!empty($filterSettings['productsPid'])) {
|
||||
$productsPid = $filterSettings['productsPid'];
|
||||
$qb->andWhere($qb->expr()->in(
|
||||
'date.product_pid',
|
||||
@@ -157,7 +157,7 @@ class ProductRepository extends AbstractRepository
|
||||
->addOrderBy('date.date_start')
|
||||
;
|
||||
|
||||
if ($filterSettings['productsPid']) {
|
||||
if (!empty($filterSettings['productsPid'])) {
|
||||
$productsPid = (array) $filterSettings['productsPid'];
|
||||
$qb->andWhere($qb->expr()->in(
|
||||
'date.product_pid',
|
||||
@@ -236,7 +236,7 @@ class ProductRepository extends AbstractRepository
|
||||
)
|
||||
;
|
||||
|
||||
if ($filterSettings['productsPid']) {
|
||||
if (!empty($filterSettings['productsPid'])) {
|
||||
$productsPid = (array) $filterSettings['productsPid'];
|
||||
$qb->andWhere($qb->expr()->in(
|
||||
'date.product_pid',
|
||||
@@ -294,7 +294,7 @@ class ProductRepository extends AbstractRepository
|
||||
->groupBy('date.date_start')
|
||||
;
|
||||
|
||||
if ($filterSettings['productsPid']) {
|
||||
if (!empty($filterSettings['productsPid'])) {
|
||||
$productsPid = (array) $filterSettings['productsPid'];
|
||||
$qb->andWhere($qb->expr()->in(
|
||||
'date.product_pid',
|
||||
|
||||
@@ -108,7 +108,7 @@ abstract class AbstractImageService implements SingletonInterface
|
||||
{
|
||||
$cropString = $image->getProperty('crop');
|
||||
$cropVariantCollection = CropVariantCollection::create((string) $cropString);
|
||||
$cropVariant = $processingInstructions['cropVariant'] ?: 'default';
|
||||
$cropVariant = ($processingInstructions['cropVariant'] ?? null) ?: 'default';
|
||||
$cropArea = $cropVariantCollection->getCropArea($cropVariant);
|
||||
|
||||
return $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image);
|
||||
|
||||
@@ -82,8 +82,8 @@ class HotelDataService implements SingletonInterface
|
||||
$hotelCategory = array_key_exists($hotelData['hotelCategory'], Hotel::$categoryLabels) ?
|
||||
Hotel::$categoryLabels[$hotelData['hotelCategory']] : null;
|
||||
|
||||
$dateStart = new \DateTime($hotelData['dateStart']);
|
||||
$dateEnd = new \DateTime($hotelData['dateEnd']);
|
||||
$dateStart = new \DateTime($hotelData['dateStart'] ?? 'now');
|
||||
$dateEnd = new \DateTime($hotelData['dateEnd'] ?? 'now');
|
||||
|
||||
return [
|
||||
'minDateStart' => $dateStart,
|
||||
|
||||
@@ -5,18 +5,15 @@ namespace EP\EpProducts\Traits;
|
||||
use EP\EpProducts\Service\FilterService;
|
||||
use EP\EpProducts\Service\SearchResultService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
|
||||
trait RequestArgumentTypeConversionTrait
|
||||
{
|
||||
|
||||
public function processSearchParamsArgument(): void
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
|
||||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
|
||||
/** @var SearchResultService $searchResultService */
|
||||
$searchResultService = $objectManager->get(SearchResultService::class);
|
||||
$searchResultService = GeneralUtility::makeInstance(SearchResultService::class);
|
||||
|
||||
if (!$this->request->hasArgument('searchParams')) {
|
||||
$searchParams = $searchResultService->getNormalizedSearchParams();
|
||||
@@ -32,11 +29,9 @@ trait RequestArgumentTypeConversionTrait
|
||||
|
||||
public function processFilterSettingsArgument(array $forcedConceptUids = []): void
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
|
||||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
|
||||
/** @var FilterService $filterService */
|
||||
$filterService = $objectManager->get(FilterService::class);
|
||||
$filterService = GeneralUtility::makeInstance(FilterService::class);
|
||||
|
||||
if (!$this->request->hasArgument('filterSettings')) {
|
||||
$filterSettings = $filterService->getNormalizedFilterSettings([], $forcedConceptUids);
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace EP\EpProducts\Utility;
|
||||
use TYPO3\CMS\Core\Http\ApplicationType;
|
||||
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
|
||||
use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
@@ -97,9 +96,7 @@ class SerializationUtility
|
||||
protected static function getImageService()
|
||||
{
|
||||
if (static::$imageService === null) {
|
||||
/** @var ObjectManager $objectManager */
|
||||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
static::$imageService = $objectManager->get(ImageService::class);
|
||||
static::$imageService = GeneralUtility::makeInstance(ImageService::class);
|
||||
}
|
||||
|
||||
return static::$imageService;
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace EP\EpProducts\Utility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
|
||||
class SettingsUtility
|
||||
{
|
||||
@@ -41,9 +40,7 @@ class SettingsUtility
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
|
||||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
$configurationManager = $objectManager->get(ConfigurationManager::class);
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$settings = GeneralUtility::removeDotsFromTS(
|
||||
$configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT)
|
||||
);
|
||||
|
||||
@@ -11,6 +11,26 @@ services:
|
||||
EP\EpProducts\Service\ProductImportSource:
|
||||
alias: EP\EpProducts\Service\ApiImportSource
|
||||
|
||||
# Fetched with GeneralUtility::makeInstance() by data processors, view helpers and utilities
|
||||
EP\EpProducts\Domain\Repository\CityRepository:
|
||||
public: true
|
||||
EP\EpProducts\Domain\Repository\ConceptRepository:
|
||||
public: true
|
||||
EP\EpProducts\Domain\Repository\CountryRepository:
|
||||
public: true
|
||||
EP\EpProducts\Domain\Repository\RegionRepository:
|
||||
public: true
|
||||
EP\EpProducts\Service\FilterService:
|
||||
public: true
|
||||
EP\EpProducts\Service\HotelDataService:
|
||||
public: true
|
||||
EP\EpProducts\Service\ProductDataService:
|
||||
public: true
|
||||
EP\EpProducts\Service\ResellerDataService:
|
||||
public: true
|
||||
EP\EpProducts\Service\SearchResultService:
|
||||
public: true
|
||||
|
||||
cache.ep_cache:
|
||||
class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
|
||||
public: true
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace EP\EpTheme\DataProcessing;
|
||||
use EP\EpProducts\Service\ResellerDataService;
|
||||
use EP\EpProducts\Traits\DbConnectionTrait;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
|
||||
@@ -77,10 +76,8 @@ class ContextProcessor implements DataProcessorInterface
|
||||
*/
|
||||
private function getResellerData()
|
||||
{
|
||||
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
|
||||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
/** @var ResellerDataService $resellerService */
|
||||
$resellerService = $objectManager->get(ResellerDataService::class);
|
||||
$resellerService = GeneralUtility::makeInstance(ResellerDataService::class);
|
||||
return $resellerService->getResellerForCurrentDomain();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class FlexFormProcessor implements DataProcessorInterface
|
||||
$data = $cObj->data;
|
||||
if (array_key_exists('pi_flexform', $data)) {
|
||||
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
|
||||
$processedData['data']['flexForm'] = $flexFormService->convertFlexFormContentToArray($data['pi_flexform']);
|
||||
$processedData['data']['flexForm'] = $flexFormService->convertFlexFormContentToArray((string)$data['pi_flexform']);
|
||||
}
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ use EP\EpProducts\Domain\Repository\RegionRepository;
|
||||
use EP\EpProducts\Service\HotelDataService;
|
||||
use EP\EpProducts\Service\ProductDataService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
|
||||
@@ -58,40 +57,38 @@ class TeaserProcessor implements DataProcessorInterface
|
||||
array $processorConfiguration,
|
||||
array $processedData
|
||||
) {
|
||||
/** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
|
||||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
|
||||
if ((int) $processedData['data']['tx_eptheme_context_city'] > 0) {
|
||||
/** @var CityRepository $repository */
|
||||
$repository = $objectManager->get(CityRepository::class);
|
||||
$repository = GeneralUtility::makeInstance(CityRepository::class);
|
||||
/** @var City $city */
|
||||
$city = $repository->findByIdentifier($processedData['data']['tx_eptheme_context_city']);
|
||||
$processedData['city'] = $city;
|
||||
}
|
||||
if ((int) $processedData['data']['tx_eptheme_context_region'] > 0) {
|
||||
/** @var RegionRepository $repository */
|
||||
$repository = $objectManager->get(RegionRepository::class);
|
||||
$repository = GeneralUtility::makeInstance(RegionRepository::class);
|
||||
/** @var Region $region */
|
||||
$region = $repository->findByIdentifier($processedData['data']['tx_eptheme_context_region']);
|
||||
$processedData['region'] = $region;
|
||||
}
|
||||
if ((int) $processedData['data']['tx_eptheme_context_country'] > 0) {
|
||||
/** @var CountryRepository $repository */
|
||||
$repository = $objectManager->get(CountryRepository::class);
|
||||
$repository = GeneralUtility::makeInstance(CountryRepository::class);
|
||||
/** @var Country $country */
|
||||
$country = $repository->findByIdentifier($processedData['data']['tx_eptheme_context_country']);
|
||||
$processedData['country'] = $country;
|
||||
}
|
||||
if ((int) $processedData['data']['tx_eptheme_context_hotel'] > 0) {
|
||||
/** @var HotelDataService $service */
|
||||
$service = $objectManager->get(HotelDataService::class);
|
||||
$service = GeneralUtility::makeInstance(HotelDataService::class);
|
||||
/** @var array $hotel */
|
||||
$hotel = $service->getTeaser($processedData['data']['tx_eptheme_context_hotel']);
|
||||
$processedData['hotel'] = $hotel;
|
||||
}
|
||||
if ((int) $processedData['data']['tx_eptheme_context_concept'] > 0) {
|
||||
/** @var ConceptRepository $repository */
|
||||
$repository = $objectManager->get(ConceptRepository::class);
|
||||
$repository = GeneralUtility::makeInstance(ConceptRepository::class);
|
||||
/** @var Concept $concept */
|
||||
$concept = $repository->findByIdentifier($processedData['data']['tx_eptheme_context_concept']);
|
||||
$processedData['concept'] = $concept;
|
||||
@@ -119,7 +116,7 @@ class TeaserProcessor implements DataProcessorInterface
|
||||
}
|
||||
|
||||
/** @var ProductDataService $productDataService */
|
||||
$productDataService = $objectManager->get(ProductDataService::class);
|
||||
$productDataService = GeneralUtility::makeInstance(ProductDataService::class);
|
||||
$processedData['teaser'] = $productDataService->getTeaser($filterSettings);
|
||||
$processedData['teaser']['forceHotelUid'] = $hotelUid;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,12 @@ class OpenGraphMetaTagManager extends \TYPO3\CMS\Seo\MetaTag\OpenGraphMetaTagMan
|
||||
*/
|
||||
protected function getPageWithImage()
|
||||
{
|
||||
$rootLineUtility = new RootlineUtility($GLOBALS['TYPO3_REQUEST']->getAttribute('frontend.page.information')->getId());
|
||||
// The manager is also asked to render in the backend, where there is no frontend page
|
||||
$pageInformation = $GLOBALS['TYPO3_REQUEST']?->getAttribute('frontend.page.information');
|
||||
if ($pageInformation === null) {
|
||||
return null;
|
||||
}
|
||||
$rootLineUtility = new RootlineUtility($pageInformation->getId());
|
||||
$rootLine = $rootLineUtility->get();
|
||||
|
||||
foreach ($rootLine as $page) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace EP\EpTheme\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Http\Stream;
|
||||
|
||||
/**
|
||||
* Points image URLs in frontend pages at the CDN (formerly EXT:replacer).
|
||||
*
|
||||
* Only images go to the CDN. The theme's own css/js/fonts are served by the production
|
||||
* host: they are already minified by Webpack, gzipped by mod_deflate and content-hashed
|
||||
* for a year of immutable browser caching, and keeping them on the page's own origin
|
||||
* means they arrive over the connection the browser has already opened rather than
|
||||
* costing a second DNS lookup and TLS handshake on the critical path.
|
||||
*
|
||||
* It also keeps them clear of the pull zone's habit of occasionally wedging a single
|
||||
* cache key (10.0s -> 504 errorcode 106 until that key is purged, while the origin
|
||||
* serves the same bytes in under 100ms). For an image that degrades one element; for
|
||||
* js/main.<hash>.js it would take every visitor's JavaScript down.
|
||||
*
|
||||
* typo3temp is scoped to generated images on purpose - assets/compressed holds other
|
||||
* extensions' concatenated css/js, which belongs on the origin for the same reason.
|
||||
* The quote character is captured rather than matched literally, so url() values
|
||||
* written with single quotes are rewritten too.
|
||||
*
|
||||
* Registered after the page resolver, so the /api/* responses of ep_products never pass here.
|
||||
*/
|
||||
final class CdnImageMiddleware implements MiddlewareInterface
|
||||
{
|
||||
private const PATTERN = '/(["\'])\/?(fileadmin|uploads|typo3temp\/assets\/(?:_processed_|images))/';
|
||||
|
||||
public function __construct(private readonly ExtensionConfiguration $extensionConfiguration)
|
||||
{
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
|
||||
$cdnBaseUrl = rtrim((string)($this->extensionConfiguration->get('ep_theme', 'cdnBaseUrl') ?? ''), '/');
|
||||
$contentType = $response->getHeaderLine('Content-Type');
|
||||
if ($cdnBaseUrl === ''
|
||||
|| !(str_starts_with($contentType, 'text/html') || str_starts_with($contentType, 'application/json'))
|
||||
) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$body = (string)$response->getBody();
|
||||
$replaced = preg_replace(self::PATTERN, '$1' . $cdnBaseUrl . '/$2', $body);
|
||||
if ($replaced === null || $replaced === $body) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$stream = new Stream('php://temp', 'rw');
|
||||
$stream->write($replaced);
|
||||
return $response->withBody($stream);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'frontend' => [
|
||||
'ep/cdn-images' => [
|
||||
'target' => \EP\EpTheme\Middleware\CdnImageMiddleware::class,
|
||||
'after' => [
|
||||
'typo3/cms-frontend/page-resolver',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -73,12 +73,16 @@ page {
|
||||
# RequestHandler turns this into `compress => false`, which PageRendererHooks forwards to
|
||||
# typo3_encore's TagRenderer (whose own default it would otherwise override).
|
||||
includeCSS {
|
||||
main = typo3_encore:EpThemeConfig:main
|
||||
main = PKG:ssch/typo3-encore:EpThemeConfig:main
|
||||
# Skips TYPO3's file path check, which would drop the entry name before typo3_encore's hook sees it
|
||||
main.external = 1
|
||||
main.disableCompression = 1
|
||||
}
|
||||
|
||||
includeJSFooter {
|
||||
main = typo3_encore:EpThemeConfig:main
|
||||
main = PKG:ssch/typo3-encore:EpThemeConfig:main
|
||||
# Skips TYPO3's file path check, which would drop the entry name before typo3_encore's hook sees it
|
||||
main.external = 1
|
||||
main.defer = 1
|
||||
main.disableCompression = 1
|
||||
}
|
||||
|
||||
@@ -12,11 +12,8 @@
|
||||
data-action="lightbox#open"
|
||||
data-lightbox-caption="Webcam {region.name}"
|
||||
title="Webcam {region.name}">
|
||||
<f:image src="{region.webcam.url}"
|
||||
loading="lazy"
|
||||
class="block w-full h-auto"
|
||||
width="330"
|
||||
alt="Webcam {region.name}" />
|
||||
<f:comment>External image: f:image only resolves FAL files since TYPO3 12</f:comment>
|
||||
<img class="block w-full h-auto" alt="Webcam {region.name}" loading="lazy" src="{region.webcam.url}" width="330" />
|
||||
</a>
|
||||
</div>
|
||||
</f:if>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# cat=CDN; type=string; label=CDN base URL for images in frontend pages (fileadmin, uploads, generated typo3temp images); empty serves them from the site itself
|
||||
cdnBaseUrl = https://cdn.ep-reisen.de/
|
||||
Reference in new issue
Block a user