Footer-Spalten: Linktexte an die Relaunch-Struktur angeglichen

Die Footer-HTML-Bloecke (pid 19, #132-#135) trugen noch die Benennungen
der alten Website (Venues, Military, Accommodation, VITEC OG, PCIe
Encoder, Interactive TV Portal ...), obwohl die Ziele seit
seo_fixes_20261109 stimmen. Migration fix_footer_link_texts_20261110:
Linktext + title-Attribut = Navigationstitel der Zielseite (Produkt-
Records per Titel), Blatt-Eintraege, die auf dieselbe Seite wie ein
frueherer Eintrag oder ein Eltern-Eintrag zeigen, entfernt, die
Uebersichts-Platzhalter Healthcare (/markets) und Telemetry (/products)
gestrichen, leere Listen bereinigt. Redaktionelle Sonderfaelle: Digital
Signage in der Solutions-Spalte -> Loesungsseite, Fx4 Series -> /product/
fx4, IP/RF Gateways & Transcoders -> /products/gateways-and-transcoding,
HelpDesk-Seite als 'HelpDesk Access' (externer Login-Link steht
darueber). Externe Links, t3://-Links und Ueberschriften unveraendert.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
orascheandClaude Fable 5.1 committed 2026-11-10 16:05:05 +01:00
1 parent be4bb44c40
commit ff6f86b829
1 file changed
+196
@@ -0,0 +1,196 @@
<?php
declare(strict_types=1);
/**
* Footer columns (pid 19, html elements #132-#135, #119): align the old-site
* link texts with the relaunch structure.
*
* The footer HTML still carries the old website's wording ("Venues",
* "Military", "VITEC OG", "PCIe Encoder" …) although every href points at
* the new pages since seo_fixes_20261109. This migration, per internal link:
*
* 1. sets the link text (and title attribute) to the target page's
* navigation title (nav_title, falling back to title);
* 2. drops leaf entries that only duplicate an earlier entry of the same
* column (several old items collapsed onto one new page);
* 3. drops the orphaned catch-all entries that point at the bare /markets
* or /products overview ("Healthcare", "Telemetry" - no equivalent page);
* 4. removes list containers left empty by 2./3.
*
* External links (LinkedIn, YouTube, HelpDesk) and t3:// links stay as they
* are. Headings (<h4>) stay.
*
* Dry run: php migrations/fix_footer_link_texts_20261110.php
* Write: php migrations/fix_footer_link_texts_20261110.php --apply
*/
error_reporting(E_ALL);
ini_set('display_errors', '1');
$apply = in_array('--apply', $argv, true);
$root = null;
$dir = __DIR__;
for ($i = 0; $i < 5; $i++) {
if (is_file($dir . '/config/system/settings.php')) { $root = $dir; break; }
$dir = dirname($dir);
}
if ($root === null) { exit("no project root\n"); }
$settings = require $root . '/config/system/settings.php';
$db = $settings['DB']['Connections']['Default'];
$pdo = new PDO(
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
$db['user'] ?? '', $db['password'] ?? '',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);
echo $apply ? "== APPLY ==\n" : "== DRY RUN (add --apply to write) ==\n";
$pageTitle = $pdo->prepare("SELECT title, nav_title FROM pages WHERE deleted = 0 AND slug = ? LIMIT 1");
$resolveTitle = static function (string $href) use ($pageTitle): ?string {
$pageTitle->execute([$href]);
$row = $pageTitle->fetch();
if (!$row) {
return null;
}
$t = trim((string)($row['nav_title'] ?? '')) ?: trim((string)($row['title'] ?? ''));
return $t !== '' ? $t : null;
};
$dropOverviewHrefs = ['/markets', '/products'];
// Product detail pages are records, not pages.
$productTitle = $pdo->prepare("SELECT title FROM tx_vitec_domain_model_product WHERE deleted = 0 AND hidden = 0 AND slug = ? LIMIT 1");
$resolveAnyTitle = static function (string $href) use ($resolveTitle, $productTitle): ?string {
if (str_starts_with($href, '/product/')) {
$productTitle->execute([substr($href, strlen('/product/'))]);
$t = trim((string)($productTitle->fetchColumn() ?: ''));
return $t !== '' ? $t : null;
}
return $resolveTitle($href);
};
/*
* Editorial overrides where the mechanical "target page title" is the wrong
* wording or the earlier redirect mapping landed on a too-generic page.
* Keyed by element uid; matched by old link text (decoded) or by href.
*/
$overrides = [
133 => [
// Solutions column: "Digital Signage" is a solution, not the product category
['matchHref' => '/products/iptv-and-digital-signage', 'href' => '/solutions/digital-signage-content-delivery'],
],
134 => [
// Fx4 is a product of its own, not the whole video-wall category
['matchText' => 'Fx4 Series', 'href' => '/product/fx4', 'text' => 'Fx4'],
// the gateways/transcoding parent has its own category page
['matchText' => 'IP/RF Gateways & Transcoders', 'href' => '/products/gateways-and-transcoding'],
],
135 => [
// sits directly below the external "Log in to HelpDesk" link
['matchHref' => '/support/login-to-helpdesk', 'text' => 'HelpDesk Access'],
],
];
$elements = $pdo->query(
"SELECT uid, bodytext FROM tt_content WHERE pid = 19 AND deleted = 0 AND CType = 'html' ORDER BY colPos, sorting"
)->fetchAll();
$update = $pdo->prepare("UPDATE tt_content SET bodytext = ?, tstamp = UNIX_TIMESTAMP() WHERE uid = ?");
foreach ($elements as $element) {
$uid = (int)$element['uid'];
$html = (string)$element['bodytext'];
$log = [];
// --- 1. rename internal links -----------------------------------------
$seen = [];
$elementOverrides = $overrides[$uid] ?? [];
$html = preg_replace_callback(
'#<a\b([^>]*?)href="(/[^"]*)"([^>]*)>([^<]*)</a>#s',
static function (array $m) use ($resolveAnyTitle, &$log, &$seen, $elementOverrides): string {
$href = $m[2];
$oldText = trim(strip_tags($m[4]));
$oldDecoded = html_entity_decode($oldText, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$forcedText = null;
foreach ($elementOverrides as $o) {
$hit = (isset($o['matchText']) && $o['matchText'] === $oldDecoded)
|| (isset($o['matchHref']) && $o['matchHref'] === $href);
if ($hit) {
if (!empty($o['href']) && $o['href'] !== $href) {
$log[] = sprintf(' > [%s] Ziel %s -> %s', $oldText, $href, $o['href']);
$href = $o['href'];
}
$forcedText = $o['text'] ?? null;
break;
}
}
$newTitle = $forcedText ?? $resolveAnyTitle($href);
$seen[$href] = ($seen[$href] ?? 0) + 1;
if ($newTitle === null) {
$log[] = sprintf(' ? [%s] -> %s (keine Seite, unveraendert)', $oldText, $href);
return $m[0];
}
$escaped = htmlspecialchars($newTitle, ENT_QUOTES | ENT_HTML5, 'UTF-8', false);
$attrs = $m[1] . 'href="' . $href . '"' . $m[3];
if (preg_match('/\stitle="[^"]*"/', $attrs)) {
$attrs = preg_replace('/\stitle="[^"]*"/', ' title="' . $escaped . '"', $attrs, 1);
} else {
$attrs .= ' title="' . $escaped . '"';
}
if ($oldDecoded !== $newTitle) {
$log[] = sprintf(' ~ [%s] -> [%s] (%s)', $oldText, $newTitle, $href);
}
return '<a' . $attrs . '>' . $escaped . '</a>';
},
$html
) ?? $html;
// --- 2.+3. drop duplicate leaf items and overview catch-alls -------------
// Parent items (a link followed by a nested <ul>) always stay; a leaf
// pointing at the same page as a parent or an earlier leaf is a duplicate.
$kept = [];
// Link texts are plain ([^<]*), so neither pattern can span from a parent
// link into its nested list.
preg_match_all('#<li>\s*<a\b[^>]*href="(/[^"]*)"[^>]*>[^<]*</a>\s*<ul>#s', $html, $parents);
foreach ($parents[1] as $parentHref) {
$kept[$parentHref] = true;
}
$html = preg_replace_callback(
'#<li>\s*<a\b[^>]*href="(/[^"]*)"[^>]*>([^<]*)</a>\s*</li>\s*#s',
static function (array $m) use (&$kept, &$log, $dropOverviewHrefs): string {
$href = $m[1];
$text = trim(strip_tags($m[2]));
if (in_array($href, $dropOverviewHrefs, true)) {
$log[] = sprintf(' - [%s] -> %s (Uebersichts-Platzhalter entfernt)', $text, $href);
return '';
}
if (isset($kept[$href])) {
$log[] = sprintf(' - [%s] -> %s (Dublette entfernt)', $text, $href);
return '';
}
$kept[$href] = true;
return $m[0];
},
$html
) ?? $html;
// --- 4. empty lists ------------------------------------------------------
$before = '';
while ($before !== $html) {
$before = $html;
$html = preg_replace('#<ul>\s*</ul>#s', '', $html) ?? $html;
}
$changed = $html !== (string)$element['bodytext'];
echo "\n##### tt_content #$uid " . ($changed ? '(AENDERUNGEN)' : '(unveraendert)') . "\n";
foreach ($log as $line) {
echo $line . "\n";
}
if ($changed && $apply) {
$update->execute([$html, $uid]);
echo " gespeichert\n";
}
}
echo $apply ? "\nFERTIG. Cache flushen + SSR purgen (Direkt-SQL loest keinen Auto-Purge aus).\n" : "\nDRY RUN beendet.\n";