fix: product pages no longer serve one visitor's back link to everyone

The "back to search results" link took ?ref= as it came and was cached
with the page, which ignores ref: the first visitor of a product page
set the link for every later one, and a javascript: URL in ref put
script into the page for all of them (verified locally).

The page now always renders the link hidden and without a target; the
backlink Stimulus controller fills in the visitor's own ?ref= and shows
it, only for URLs of the same origin. The label stays "zurück zum
Suchergebnis": refs only come from the search, and the old check
(strpos() !== -1) always picked that label anyway.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
frommeandClaude Opus 5.5 committed 2026-10-27 11:28:11 +01:00
1 parent b428ce9086
commit 170e14dae5
3 files changed
+29 -12

No files matched your search

@@ -91,10 +91,6 @@ class ProductController extends ActionController
$isProductWithGroupInquiryForm = ($concept !== null) && \in_array($concept->getUid(), $groupInquiryFormConceptUids);
$nonBookableInfo = $product->getNonBookableInfo();
// Assemble backlink
$backlinkUrl = $this->request->getQueryParams()['ref'] ?? null;
$backLinkLabel = strpos((string)$backlinkUrl, 'suche') !== -1 ? 'zurück zum Suchergebnis' : 'zurück';
$this->view->assignMultiple([
'hotel' => $hotel,
'product' => $product,
@@ -106,8 +102,6 @@ class ProductController extends ActionController
'currentPageUid' => $this->request->getAttribute('frontend.page.information')->getId(),
'dateFrom' => $dateFrom,
'dateTo' => $dateTo,
'referringUrl' => $backlinkUrl,
'backlinkLabel' => $backLinkLabel,
'nonBookableInfo' => $nonBookableInfo,
'pickupsOverview' => $this->pickupOverviewService->getOverview($product, $hotel),
]);
@@ -0,0 +1,25 @@
import { Controller } from '@hotwired/stimulus'
// The page is cached alike for every visitor, so the link back to the visitor's own search is
// taken from ?ref= here, and only when it points to this site: anything else, javascript: URLs
// included, has a different origin
export default class extends Controller {
connect() {
const ref = new URLSearchParams(window.location.search).get('ref')
if (null === ref) {
return
}
let url
try {
url = new URL(ref, window.location.href)
} catch {
return
}
if (url.origin !== window.location.origin) {
return
}
this.element.href = url.href
this.element.classList.remove('hidden')
}
}
@@ -55,12 +55,10 @@
</div>
</f:if>
</div>
<f:if condition="{referringUrl}">
<a href="{referringUrl}" class="button bg-button button--small mt-8">
{ep:icon(icon: 'chevron-left', class: 'w-4 h-4 text-white')}
<span>{backlinkLabel}</span>
</a>
</f:if>
<a class="button bg-button button--small mt-8 hidden" data-controller="backlink">
{ep:icon(icon: 'chevron-left', class: 'w-4 h-4 text-white')}
<span>zurück zum Suchergebnis</span>
</a>
</div>
</div>
<f:render partial="Badge" arguments="{badge: product.badge}"/>