diff --git a/migrations/fix_footer_link_texts_20261110.php b/migrations/fix_footer_link_texts_20261110.php new file mode 100644 index 0000000..d29f689 --- /dev/null +++ b/migrations/fix_footer_link_texts_20261110.php @@ -0,0 +1,196 @@ +) 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