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:
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";
|
||||
Reference in new issue
Block a user