feat: products can have their own text in the subsite popup

The popup that announces the switch to another site (e.g. from a
teaser on ep-reisen.de to semesterende-skireisen.de) showed the text of
the product's concept for every product. A product now has an optional
popup text (new "Popup" tab) that replaces the concept's; empty, the
concept's text stays. Title, image and button remain the concept's, and
whether a popup appears at all is still up to the concept.

Teasers pass the product to the popup request. Pages cached before the
deploy request it without one and get the concept's text, as before.

The new column tx_epproducts_domain_model_product.popup_text is the only
pending schema change; the deploy's extension:setup adds it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
frommeandClaude Opus 5.5 committed 2026-10-27 11:41:18 +01:00
1 parent 170e14dae5
commit 903292d84c
6 files changed
+42 -4

No files matched your search

@@ -4,14 +4,21 @@ namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
use EP\EpProducts\Domain\Model\Concept;
use EP\EpProducts\Domain\Model\Product;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class AjaxTeaserPopupController extends ActionController
{
public function conceptAction(Concept $concept, string $targetUrl): ResponseInterface
public function conceptAction(Concept $concept, string $targetUrl, ?Product $product = null): ResponseInterface
{
// A product can have its own text; title, image and button stay those of the concept
$popupText = (string)$product?->getPopupText();
if ('' === trim(strip_tags($popupText))) {
$popupText = $concept->getPopupText();
}
$this->view->assignMultiple([
'concept' => $concept,
'popupText' => $popupText,
'targetUrl' => $targetUrl,
]);
return $this->htmlResponse();
@@ -279,6 +279,11 @@ class Product extends AbstractEntity implements TeaserInterface
*/
protected $nonBookableInfo;
/**
* @var string
*/
protected $popupText = '';
public function __construct()
{
$this->initStorageObjects();
@@ -1146,6 +1151,18 @@ class Product extends AbstractEntity implements TeaserInterface
$this->tourThumbnail = $tourThumbnail;
}
public function getPopupText()
{
return $this->popupText;
}
public function setPopupText($popupText): static
{
$this->popupText = $popupText;
return $this;
}
public function getTourLink()
{
return $this->tourLink;
@@ -41,6 +41,7 @@ return [
tour_thumbnail,
--div--;Bewertung, --palette--;;rating, testimonials,
--div--;Buchbarkeit, non_bookable_dates,
--div--;Popup, popup_text,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent',
],
],
@@ -876,6 +877,18 @@ return [
'maxitems' => 1,
],
],
'popup_text' => [
'exclude' => 0,
'label' => 'Popup Text',
'description' => 'Ersetzt für dieses Produkt den Popup-Text des Konzepts. Leer: Text des Konzepts. Ob ein Popup erscheint, legt weiterhin das Konzept fest.',
'config' => [
'type' => 'text',
'cols' => 40,
'rows' => 5,
'eval' => 'trim',
'enableRichtext' => true,
],
],
'non_bookable_info' => [
'exclude' => 0,
'label' => 'Infobox nicht buchbar',
+2 -1
View File
@@ -65,7 +65,8 @@ CREATE TABLE tx_epproducts_domain_model_product
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',
popup_text text NOT NULL,
min_price int(11) unsigned NOT NULL default '0',
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,
@@ -71,7 +71,7 @@
<div class="flex-1 flex flex-col space-y-1 px-2 py-4 md:p-4">
<a href="{targetUrl}" class="peer"
<f:if condition="{concept.popupText} && {settings.globalConceptCode} != {concept.code}">
hx-get="{ep:uri.ajax(action: 'concept', controller: 'AjaxTeaserPopup', arguments: '{concept: concept.uid, targetUrl: targetUrl}', format: 'html', pageUid: settings.defaultAjaxUid)}"
hx-get="{ep:uri.ajax(action: 'concept', controller: 'AjaxTeaserPopup', arguments: '{concept: concept.uid, product: product.uid, targetUrl: targetUrl}', format: 'html', pageUid: settings.defaultAjaxUid)}"
hx-target="body"
hx-swap="beforeend"
</f:if>
@@ -28,7 +28,7 @@
<h3>
{concept.name}
</h3>
{concept.popupText -> f:format.html()}
{popupText -> f:format.html()}
<a href="{targetUrl}" target="_blank" class="button bg-button">
{concept.popupButtonLabel}
</a>