Files
VITEC-website/packages/vitec/Classes/Service/LinkResolver.php
Oliver Rasche 48da8bccd8 Solution detail page and a single LinkResolver
Closes two annex items that turned out to be the same defect seen from two
sides: markets could link to a detail page, solutions could not, and the code
that turns a page uid into a URL existed four times over.

detail_page for Solution (B-11)
- ext_tables.sql, TCA (group on pages, maxitems 1, in showitem behind slug)
  and Domain\Model\Solution mirror the Market field exactly
- SolutionShowJsonRenderer emits detailUrl
- ModelcardJsonRenderer serialized no detailUrl at all: its shared
  market|solution branch never carried the field. Market modelcards therefore
  gain a link here too, not just solutions.

Service\LinkResolver (B-12)
- one implementation replaces four private copies (Market list/show, Usecase
  list/show). The copies had drifted: Market returned null on failure, Usecase
  an empty string. The contract is now explicit -- null means "no link".
- Usecase keeps a thin resolvePageUrl() wrapper appending ?? '', because it
  concatenates paths and a null would tear the strings apart.
- LocationsJsonRenderer and NewsJsonRenderer stay out on purpose: they resolve
  a full parameter construct resp. slug paths plus canonical, which is not
  page-uid-to-URL and does not fit the same contract.

Requires on the server, the column exists in code only:
    vendor/bin/typo3 database:updateschema "*.add,*.change"
    vendor/bin/typo3 cache:flush
2026-08-10 15:27:16 +02:00

49 lines
1.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Service;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
/**
* Resolves a page uid to its frontend URL.
*
* A headless renderer cannot hand a raw page uid to the frontend — React has no
* way to turn `12` into a path. Every field that carries a link (`detailUrl`,
* `headerLink`, the usecase list's item links) is therefore resolved server side.
*
* The resolution goes through `typoLink_URL`, so route enhancers and the slug of
* the target page are honoured; building the path by hand would bypass both.
*
* This lived as a private copy in four renderers (Market list/show, Usecase
* list/show). The copies had drifted in their failure handling — two returned
* null, two an empty string — which is why the contract is spelled out here:
* **null means "no link"**, and callers that need a string coalesce it.
*/
final class LinkResolver
{
/**
* Returns null when no page is set or the link cannot be resolved.
*
* Never throws: a broken link must not take down the whole JSON response,
* so a failure downgrades to "no link" like an empty field would.
*/
public static function pageUrl(int $pageUid): ?string
{
if ($pageUid <= 0) {
return null;
}
try {
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$url = $cObj->typoLink_URL(['parameter' => (string)$pageUid]);
return $url !== '' ? $url : null;
} catch (\Throwable $e) {
return null;
}
}
}