- Success Stories: full migration from old site (48/48, audited), markets n:n, quotation content block, columns content block, pretty SEO detail URLs via SuccessStoryPathRewrite middleware, list/detail split (list page 4 / story page), detailUrl/backUrl - New plugins: VITEC Locations (grid/list/map + RTE map text), VITEC Customer Logos (color/bw logic, only-show-selected), VITEC Card (one plugin for product/story/market/solution with reloading FlexForm + custom backend preview renderer) - Eventlist: layout dropdown (list/grid/teaserbar) in settings - Hero section CB: Images/Video tabs, background video + overlay - Product JSON: full category rootline (parents), fixed missing ConnectionPool import (all-products crash), category tree map - Backend preview CSS: container-query responsive (narrow columns) - Docs: ISO architecture spec, root + extension READMEs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
2.9 KiB
PHP
Executable File
80 lines
2.9 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Evomedien\Vitec\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\Database\ConnectionPool;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* Pretty detail URLs for Success Stories.
|
|
*
|
|
* Public URL: /success-stories/<slug> (old-site SEO URLs)
|
|
* Real page: /success-stories/story/<slug> (subpage holding ONLY the
|
|
* usecaseshow plugin)
|
|
*
|
|
* TYPO3's page router always resolves /success-stories/<slug> to the LIST
|
|
* page (longest page-slug prefix), so the detail subpage could never answer
|
|
* that URL. This middleware rewrites the request path internally — before
|
|
* page resolution — whenever <slug> matches an existing (visible) success
|
|
* story. Real subpages of /success-stories are unaffected: no story match,
|
|
* no rewrite. The browser URL never changes.
|
|
*
|
|
* Exception-safe: any failure leaves the request untouched.
|
|
*/
|
|
final class SuccessStoryPathRewrite implements MiddlewareInterface
|
|
{
|
|
private const LIST_PATH = '/success-stories';
|
|
private const DETAIL_SEGMENT = 'story';
|
|
|
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
|
{
|
|
$debug = 'no-match';
|
|
try {
|
|
$path = $request->getUri()->getPath();
|
|
if (preg_match('#^' . self::LIST_PATH . '/([a-zA-Z0-9\-]+)/?$#', $path, $m) === 1
|
|
&& $m[1] !== self::DETAIL_SEGMENT
|
|
) {
|
|
if ($this->isStorySlug($m[1])) {
|
|
$uri = $request->getUri()->withPath(
|
|
self::LIST_PATH . '/' . self::DETAIL_SEGMENT . '/' . $m[1]
|
|
);
|
|
$request = $request->withUri($uri);
|
|
$debug = 'rewritten';
|
|
} else {
|
|
$debug = 'slug-not-found:' . $m[1];
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$debug = 'exception:' . substr($e->getMessage(), 0, 60);
|
|
}
|
|
|
|
// TEMP-DIAG header (remove after verification)
|
|
return $handler->handle($request)->withHeader('X-Vitec-Story-Rewrite', $debug);
|
|
}
|
|
|
|
private function isStorySlug(string $slug): bool
|
|
{
|
|
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
|
->getQueryBuilderForTable('tx_vitec_domain_model_usecase');
|
|
$row = $qb
|
|
->select('uid')
|
|
->from('tx_vitec_domain_model_usecase')
|
|
->where(
|
|
$qb->expr()->eq('slug', $qb->createNamedParameter($slug)),
|
|
$qb->expr()->eq('deleted', 0),
|
|
$qb->expr()->eq('hidden', 0)
|
|
)
|
|
->setMaxResults(1)
|
|
->executeQuery()
|
|
->fetchAssociative();
|
|
|
|
return $row !== false;
|
|
}
|
|
}
|