) 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( '#]*?)href="(/[^"]*)"([^>]*)>([^<]*)#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 '' . $escaped . ''; }, $html ) ?? $html; // --- 2.+3. drop duplicate leaf items and overview catch-alls ------------- // Parent items (a link followed by a nested