), product category page * (/products/), market or solution page. * 2. Queues a 301 sys_redirect for every old path (with and without * trailing slash) so external backlinks land correctly once the * datapath.com domains point at this site. * * Dry run: php migrations/fix_datapath_links_20261110.php * Write: php migrations/fix_datapath_links_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"; /* Old path (no trailing slash) => new target path. */ $mapping = [ // Aetria '/datapath-products/aetria-control-room-solutions' => '/products/aetria', '/datapath-products/aetria-control-room-solutions/aetria-overview' => '/products/aetria', '/datapath-products/aetria-control-room-solutions/aetria-network-manager' => '/product/aetria-network-manager', '/datapath-products/aetria-control-room-solutions/aetria-touch' => '/product/aetria-touch', '/datapath-products/aetria-control-room-solutions/aetria-wallcontrol' => '/product/aetria-wallcontrol', '/datapath-products/aetria-control-room-solutions/operator-workstations' => '/products/operator-workstations', // AVoIP / KVM '/datapath-products/arqa-avoip-with-kvm' => '/product/arqa', '/datapath-products/video-distribution-kvm' => '/products/av-over-ip-and-kvm', '/datapath-products/video-distribution-kvm/aligo-4k-avoip-with-kvm' => '/products/aligo', '/datapath-products/video-distribution-kvm/aligo-qtx100-quad-transmitter' => '/product/qtx100', '/datapath-products/video-distribution-kvm/aligo-rx100-receiver' => '/product/rx100', '/datapath-products/video-distribution-kvm/aligo-tx100-transmitter' => '/product/tx100', // Capture & graphics '/datapath-products/graphics-products' => '/products/capture-and-graphics', '/datapath-products/graphics-products/image2k' => '/product/image-2k', '/datapath-products/graphics-products/image4k' => '/product/image-4k', '/datapath-products/sqx-ip-capture' => '/products/activesqx', '/datapath-products/vision-range' => '/products/vision-video-capture-cards', '/datapath-products/video-capture-cards/capture-card-features' => '/products/vision-video-capture-cards', '/datapath-products/video-capture-cards/vision-range/visionrgb-e1s' => '/product/visionrgb-e1s', '/datapath-products/video-capture-cards/vision-range/visionsd41s' => '/product/visionsd4-1s', '/datapath-products/video-capture-cards/vision-range/visionsdi2' => '/products/vision-video-capture-cards', '/datapath-products/video-capture-cards/visionav-range' => '/product/visionav', '/datapath-products/video-capture-cards/visionav-range/visionhd4' => '/product/visionhd4', '/datapath-products/video-capture-cards/visionsc-range' => '/products/vision-video-capture-cards', '/datapath-products/video-capture-cards/visionsc-range/visionsc-dp2' => '/product/visionsc-dp2', '/datapath-products/video-capture-cards/visionsc-range/visionsc-hd4' => '/product/visionsc-hd4', '/datapath-products/video-capture-cards/visionsc-range/visionsc-uhd2' => '/product/visionsc-uhd2', // Multi-display / Fx4 / Hx4 '/datapath-products/multi-display-controllers/datapath-fx4' => '/product/fx4', '/datapath-products/multi-display-controllers/datapath-fx4-sdi' => '/product/fx4-sdi', '/datapath-products/multi-display-controllers/datapath-hx4' => '/product/hx4', '/datapath-products/multi-display-controllers/multi-display-overview' => '/products/x-series-multi-display-processors', '/datapath-products/multi-display-controllers/video-wall-designer-software' => '/products/video-wall-management-software', // Video walls / VSN / WallControl '/datapath-products/video-wall-control/industrial-wall-controllers' => '/products/vsn-series-video-wall-controllers', '/datapath-products/video-wall-control/video-wall-controllers' => '/products/vsn-series-video-wall-controllers', '/datapath-products/video-wall-control/vsn-11-series' => '/products/vsn-series-video-wall-controllers', '/datapath-products/video-wall-control/vsn-9-series' => '/products/vsn-series-video-wall-controllers', '/datapath-products/video-wall-controllers' => '/products/video-wall-controllers-and-management', '/datapath-products/video-wall-controllers/datapath-fx4' => '/product/fx4', '/datapath-products/video-wall-controllers/video-wall-software/wallcontrol-10-software' => '/product/wall-control-10', '/datapath-products/video-wall-controllers/vsn-series' => '/products/vsn-series-video-wall-controllers', '/datapath-products/video-wall-management-software' => '/products/video-wall-management-software', '/datapath-products/video-wall-software/wallcontrol-10-software' => '/product/wall-control-10', // Accessories (no equivalent records - portfolio page) '/datapath-products/components-accessories/components-accessories-overview/displayport-accessories' => '/products', '/datapath-products/components-accessories/pcie-backplanes/express11-g3' => '/products', // Old market / solution names '/markets/airport-and-aviation' => '/markets/transport/airports', '/markets/corporate' => '/markets/corporation', '/markets/emergency-operation-center' => '/markets/public-safety/emergency-operation-centres', '/markets/events-and-experiences' => '/markets/sports-and-entertainment/events-and-experiences', '/markets/military-control-room' => '/markets/defense-and-security', '/markets/network-operation-center' => '/solutions/control-room-command-centre-solutions/control-room-platforms-soc-noc-command-centres', '/markets/retail-and-hospitality' => '/markets/hospitality', '/markets/security-and-surveillance' => '/solutions/security-surveillance-solutions', '/markets/traffic-management-control-centers' => '/markets/government/traffic-and-smart-mobility', '/markets/utilities' => '/markets/critical-national-infrastructure/energy-and-utilities', '/video-wall-solutions' => '/solutions/video-wall-data-visualisation', '/video-wall-solutions/retail-events-and-experiences' => '/markets/sports-and-entertainment/events-and-experiences', '/video-wall-solutions/video-wall-for-security-suites' => '/solutions/security-surveillance-solutions', ]; /* ------------------------------------------------------------------ */ /* Sanity: every target must exist (page slug, or product slug). */ /* ------------------------------------------------------------------ */ $pageBySlug = $pdo->prepare("SELECT uid FROM pages WHERE deleted = 0 AND hidden = 0 AND slug = ? LIMIT 1"); $productBySlug = $pdo->prepare("SELECT uid FROM tx_vitec_domain_model_product WHERE deleted = 0 AND hidden = 0 AND hideonwebsite = 0 AND slug = ? LIMIT 1"); $missing = 0; foreach (array_unique(array_values($mapping)) as $target) { if (str_starts_with($target, '/product/')) { $productBySlug->execute([substr($target, strlen('/product/'))]); if ((int)$productBySlug->fetchColumn() > 0) { continue; } } else { $pageBySlug->execute([$target]); if ((int)$pageBySlug->fetchColumn() > 0) { continue; } } echo " !! Ziel fehlt: $target\n"; $missing++; } if ($missing > 0) { echo "$missing Ziele nicht gefunden - Mapping pruefen.\n"; if ($apply) { exit("Abbruch.\n"); } } /* ------------------------------------------------------------------ */ /* 1. Rewrite hrefs in news bodytext/teaser */ /* ------------------------------------------------------------------ */ echo "1. Links in News-Texten ersetzen:\n"; $findNews = $pdo->prepare("SELECT COUNT(*) FROM tx_news_domain_model_news WHERE deleted = 0 AND (bodytext LIKE ? OR teaser LIKE ?)"); $fixNews = $pdo->prepare( "UPDATE tx_news_domain_model_news SET bodytext = REPLACE(bodytext, ?, ?), teaser = REPLACE(teaser, ?, ?), tstamp = UNIX_TIMESTAMP() WHERE deleted = 0 AND (bodytext LIKE ? OR teaser LIKE ?)" ); $totalHits = 0; foreach ($mapping as $old => $new) { // Both href variants: with and without trailing slash; plus the one // broken absolute form (http://datapath-products/...). $variants = ['href="' . $old . '/"', 'href="' . $old . '"', 'href="http:/' . $old . '/"']; foreach ($variants as $needle) { $like = '%' . $needle . '%'; $findNews->execute([$like, $like]); $count = (int)$findNews->fetchColumn(); if ($count === 0) { continue; } $totalHits += $count; echo " $needle -> href=\"$new\" ($count Record(s))\n"; if ($apply) { $fixNews->execute([$needle, 'href="' . $new . '"', $needle, 'href="' . $new . '"', $like, $like]); } } } echo " Summe Record-Treffer: $totalHits\n"; /* ------------------------------------------------------------------ */ /* 2. 301 redirects for the old paths */ /* ------------------------------------------------------------------ */ echo "2. Redirects anlegen:\n"; $hasRedirect = $pdo->prepare("SELECT COUNT(*) FROM sys_redirect WHERE deleted = 0 AND source_path = ?"); $insertRedirect = $pdo->prepare( "INSERT INTO sys_redirect (pid, createdon, updatedon, source_host, source_path, target, target_statuscode) VALUES (0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '*', ?, ?, 301)" ); $newRedirects = 0; foreach ($mapping as $old => $new) { foreach ([$old, $old . '/'] as $source) { $hasRedirect->execute([$source]); if ((int)$hasRedirect->fetchColumn() > 0) { continue; } $newRedirects++; if ($apply) { $insertRedirect->execute([$source, $new]); } } } echo " $newRedirects neue Redirects (mit + ohne Slash; bestehende unangetastet)\n"; echo $apply ? "FERTIG. Cache flushen.\n" : "DRY RUN beendet.\n";