Seobility-Report 2026-11-10: Backend-Befunde umgesetzt
Crawl nach dem Footer-Fix: 1.094 Seiten (vorher 9). Backend-Anteil:
- Migration seo_report_fixes_20261110: Platzhalter-SEO-Titel auf /solutions
geleert; News mit verdoppeltem Titel/Slug und Slugs mit Slash bereinigt
(+Redirects); 8 Tippfehler; zweite Welle toter Alt-Links (datapath.co.uk-
Absolutlinks, utm-Varianten, /resource-center, /services, /contact-sales,
/aetria-use-cases, /video-wall-solutions ...) in 48 News ersetzt + 101
Redirects; Record-Shells /product, /news/article, /success-stories/story
leiten auf ihre Uebersichten; Meta-Description /datasheets; WordPress-
Reste in den 17 SEO-Artikeln entfernt; 53 News-Zeiger-Records (type=1
auf alte /en/news-Seiten, ohne eigenen Text) bekommen den Seiteninhalt
(Text + Bilder) in den Record und werden normale Artikel - die alten
Seiten leiten per 301 auf die Artikel.
- VitecMetaHandler: kurze Seitentitel ohne SEO-Titel erhalten ihren
Bereich als Kontext ('Aligo | AV over IP & KVM | VITEC').
- ProcessedFileReferenceToArray: Alt-Text-Fallback (Referenz-Titel, dann
Dateititel) fuer Content-Block-Bilder.
- UsecaseListJsonRenderer: Testimonial-Baender liefern nur noch Stories
mit Zitat, aktueller Markt zuerst, max. 12 (vorher alle 70 = 226 kB auf
jeder Markt-/Solution-Unterseite, identische Bloecke auf ~90 Seiten).
- NewsJsonRenderer: paginierte Listen-Payloads 15 Min. im hash-Cache
(USER_INT rendert sonst jede Variante neu; 50 langsame URLs im Report).
- robots.txt host-abhaengig: dev bekommt robots-dev.txt mit der dev-Sitemap
(Seobility fand '0 Sitemaps', weil die Sitemap-Zeile auf www zeigte).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
5121eaa000
commit
8a652063c5
7 files changed
+590
-1
No files matched your search
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Fixes from the Seobility crawl of 2026-11-10 (1,094 pages).
|
||||
*
|
||||
* 1. Placeholder SEO title "Title for search engines [seo_title]" on /solutions.
|
||||
* 2. News records whose title/path_segment were saved twice in a row.
|
||||
* 3. News path_segments that contain slashes (imported from old URL paths).
|
||||
* 4. Typos reported by the crawler (content tables).
|
||||
* 5. Second wave of dead old-site links: /resource-center, /services,
|
||||
* /contact-sales, /aetria-use-cases, /video-wall-solutions, absolute
|
||||
* datapath.co.uk / datapath-us.com links and tracking-parameter variants.
|
||||
* 6. "http://www.vitec.com./" (trailing dot) links.
|
||||
* 7. Redirects: record shells (/product, /news/article, /success-stories/
|
||||
* story) onto their overviews, plus a few orphaned product URLs.
|
||||
* 8. Meta description for /datasheets.
|
||||
* 9. WordPress remnants inside the 17 SEO articles (lazy-load placeholder
|
||||
* images, leftover script fragment, wp-/data- attributes, bare text).
|
||||
* 10. News records of type "internal page" pointing at the old /en/news
|
||||
* pages: their body lives only on those pages (which now redirect onto
|
||||
* the empty article URLs). The page content is copied into the record,
|
||||
* images become fal_media, the record becomes a regular article, and
|
||||
* every old page gets its 301.
|
||||
*
|
||||
* Dry run: php migrations/seo_report_fixes_20261110.php
|
||||
* Write: php migrations/seo_report_fixes_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";
|
||||
$now = time();
|
||||
|
||||
$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)"
|
||||
);
|
||||
$redirectsAdded = 0;
|
||||
$addRedirect = static function (string $source, string $target) use ($hasRedirect, $insertRedirect, $apply, &$redirectsAdded): void {
|
||||
$hasRedirect->execute([$source]);
|
||||
if ((int)$hasRedirect->fetchColumn() > 0) {
|
||||
return;
|
||||
}
|
||||
$redirectsAdded++;
|
||||
if ($apply) {
|
||||
$insertRedirect->execute([$source, $target]);
|
||||
}
|
||||
};
|
||||
$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 slug = ? LIMIT 1");
|
||||
$newsBySegment = $pdo->prepare("SELECT uid FROM tx_news_domain_model_news WHERE deleted = 0 AND path_segment = ? LIMIT 1");
|
||||
$targetExists = static function (string $target) use ($pageBySlug, $productBySlug, $newsBySegment): bool {
|
||||
if (str_starts_with($target, '/product/')) {
|
||||
$productBySlug->execute([substr($target, 9)]);
|
||||
return (int)$productBySlug->fetchColumn() > 0;
|
||||
}
|
||||
if (str_starts_with($target, '/news/article/')) {
|
||||
$newsBySegment->execute([substr($target, 14)]);
|
||||
return (int)$newsBySegment->fetchColumn() > 0;
|
||||
}
|
||||
$pageBySlug->execute([$target]);
|
||||
return (int)$pageBySlug->fetchColumn() > 0;
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n1. Platzhalter-SEO-Titel/-Beschreibungen\n";
|
||||
$rows = $pdo->query("SELECT uid, slug, seo_title FROM pages WHERE deleted = 0 AND (seo_title LIKE 'Title for search engines%' OR description LIKE 'Description for search engines%')")->fetchAll();
|
||||
foreach ($rows as $r) {
|
||||
echo " #{$r['uid']} {$r['slug']}: seo_title/description geleert\n";
|
||||
if ($apply) {
|
||||
$pdo->prepare("UPDATE pages SET seo_title = IF(seo_title LIKE 'Title for search engines%', '', seo_title), description = IF(description LIKE 'Description for search engines%', '', description), tstamp = ? WHERE uid = ?")->execute([$now, $r['uid']]);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n2. News mit verdoppeltem Titel / path_segment\n";
|
||||
$halve = static function (string $s): ?string {
|
||||
$len = mb_strlen($s);
|
||||
if ($len < 20 || $len % 2 !== 0) { return null; }
|
||||
$h = mb_substr($s, 0, intdiv($len, 2));
|
||||
return $h === mb_substr($s, intdiv($len, 2)) ? $h : null;
|
||||
};
|
||||
foreach ($pdo->query("SELECT uid, title, path_segment FROM tx_news_domain_model_news WHERE deleted = 0 AND CHAR_LENGTH(title) > 40") as $r) {
|
||||
$newTitle = $halve((string)$r['title']);
|
||||
if ($newTitle === null) { continue; }
|
||||
$seg = (string)$r['path_segment'];
|
||||
$newSeg = $seg;
|
||||
// doubled slug: "a-b-ca-b-c" -> "a-b-c"
|
||||
$segLen = strlen($seg);
|
||||
if ($segLen % 2 === 0 && substr($seg, 0, intdiv($segLen, 2)) === substr($seg, intdiv($segLen, 2))) {
|
||||
$newSeg = substr($seg, 0, intdiv($segLen, 2));
|
||||
}
|
||||
echo " #{$r['uid']}: Titel -> [$newTitle]" . ($newSeg !== $seg ? ", Slug -> $newSeg (+Redirect)" : '') . "\n";
|
||||
if ($apply) {
|
||||
$pdo->prepare("UPDATE tx_news_domain_model_news SET title = ?, path_segment = ?, tstamp = ? WHERE uid = ?")->execute([$newTitle, $newSeg, $now, $r['uid']]);
|
||||
}
|
||||
if ($newSeg !== $seg) {
|
||||
$addRedirect('/news/article/' . $seg, '/news/article/' . $newSeg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n3. News path_segment mit Slash\n";
|
||||
foreach ($pdo->query("SELECT uid, path_segment FROM tx_news_domain_model_news WHERE deleted = 0 AND path_segment LIKE '%/%'") as $r) {
|
||||
$old = (string)$r['path_segment'];
|
||||
$new = basename($old);
|
||||
$newsBySegment->execute([$new]);
|
||||
$taken = (int)$newsBySegment->fetchColumn();
|
||||
if ($taken > 0 && $taken !== (int)$r['uid']) {
|
||||
$new .= '-' . $r['uid'];
|
||||
}
|
||||
echo " #{$r['uid']}: $old -> $new (+Redirect)\n";
|
||||
if ($apply) {
|
||||
$pdo->prepare("UPDATE tx_news_domain_model_news SET path_segment = ?, tstamp = ? WHERE uid = ?")->execute([$new, $now, $r['uid']]);
|
||||
}
|
||||
$addRedirect('/news/article/' . $old, '/news/article/' . $new);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n4. Tippfehler\n";
|
||||
$typos = [
|
||||
'continously' => 'continuously', 'continous' => 'continuous', 'indpendent' => 'independent',
|
||||
'aircrafts' => 'aircraft', 'sponser' => 'sponsor', 'auxillary' => 'auxiliary',
|
||||
'processer' => 'processor', 'accomodation' => 'accommodation',
|
||||
'Accomodation' => 'Accommodation', 'Continous' => 'Continuous',
|
||||
];
|
||||
$typoTargets = [
|
||||
'tt_content' => ['bodytext', 'header'],
|
||||
'tx_news_domain_model_news' => ['bodytext', 'teaser', 'title'],
|
||||
'tx_vitec_domain_model_usecase' => ['description', 'teaser'],
|
||||
];
|
||||
foreach ($typos as $wrong => $right) {
|
||||
foreach ($typoTargets as $table => $fields) {
|
||||
foreach ($fields as $field) {
|
||||
$n = (int)$pdo->query("SELECT COUNT(*) FROM $table WHERE deleted = 0 AND $field LIKE " . $pdo->quote('%' . $wrong . '%'))->fetchColumn();
|
||||
if ($n === 0) { continue; }
|
||||
echo " $wrong -> $right: $table.$field ($n)\n";
|
||||
if ($apply) {
|
||||
$pdo->exec("UPDATE $table SET $field = REPLACE($field, " . $pdo->quote($wrong) . ", " . $pdo->quote($right) . "), tstamp = $now WHERE deleted = 0 AND $field LIKE " . $pdo->quote('%' . $wrong . '%'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n5. Tote Alt-Links (zweite Welle, inkl. datapath.co.uk / utm-Varianten)\n";
|
||||
$awardSeg = 'aetria-award-wins-set-the-tone-for-datapaths-best-ever-ise-week';
|
||||
$newsBySegment->execute([$awardSeg]);
|
||||
$awardTarget = (int)$newsBySegment->fetchColumn() > 0 ? '/news/article/' . $awardSeg : '/insights/news';
|
||||
$mapping2 = [
|
||||
'/resource-center/legacy-products' => '/products/capture-and-graphics',
|
||||
'/resource-center/legacy-products/imagedp4' => '/products/image-graphics-cards',
|
||||
'/resource-center/legacy-products/datapath-x4' => '/products/video-wall-controllers-and-management',
|
||||
'/resource-center/legacy-products/datapath-dl8' => '/products/video-wall-controllers-and-management',
|
||||
'/legacy-products/imagedp4-plus' => '/products/image-graphics-cards',
|
||||
'/resource-center/get-support' => '/support',
|
||||
'/services/premium-service-package' => '/support/support-plans',
|
||||
'/services/training' => '/support/training',
|
||||
'/contact-sales' => '/contact/contact-form',
|
||||
'/aetria-use-cases/oil-and-gas-control-room' => '/markets/critical-national-infrastructure/energy-and-utilities',
|
||||
'/aetria-use-cases/energy-trading-and-dispatch' => '/markets/critical-national-infrastructure/energy-and-utilities',
|
||||
'/aetria-use-cases/cctv-for-university-campus' => '/markets/education',
|
||||
'/video-wall-solutions/commercial-video-walls' => '/solutions/video-wall-data-visualisation',
|
||||
'/video-wall-solutions/control-rooms' => '/solutions/control-room-command-centre-solutions',
|
||||
'/video-wall-solutions/projection-mapping' => '/solutions/video-wall-data-visualisation',
|
||||
'/video-wall-solutions/video-wall-for-security-suites' => '/solutions/security-surveillance-solutions',
|
||||
'/video-wall-solutions' => '/solutions/video-wall-data-visualisation',
|
||||
'/insights/success-stories' => '/success-stories',
|
||||
'/datapath-products' => '/products',
|
||||
'/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-command-center' => '/product/aetria-command-center',
|
||||
'/datapath-products/aetria-control-room-solutions/aetria-workstation' => '/product/aetria-workstation',
|
||||
'/datapath-products/aetria-control-room-solutions/aetria-wallcontrol' => '/product/aetria-wallcontrol',
|
||||
'/datapath-products/video-wall-controllers/vsn-v3' => '/product/vsn-v3',
|
||||
'/datapath-products/video-wall-controllers/vsn' => '/products/vsn-series-video-wall-controllers',
|
||||
'/datapath-products/video-wall-controllers/vsn-series' => '/products/vsn-series-video-wall-controllers',
|
||||
'/datapath-products/video-wall-controllers/vsnmicro-600' => '/product/vsnmicro-600',
|
||||
'/datapath-products/video-wall-controllers/datapath-fx4' => '/product/fx4',
|
||||
'/datapath-products/video-distribution-kvm/aligo-4k-avoip-with-kvm' => '/products/aligo',
|
||||
'/datapath-products/video-distribution-kvm' => '/products/av-over-ip-and-kvm',
|
||||
'/datapath-products/video-capture-cards/visionsc-range/visionsc-uhd2' => '/product/visionsc-uhd2',
|
||||
'/datapath-products/video-capture-cards/visionsc-range/visionsc-hd4' => '/product/visionsc-hd4',
|
||||
'/datapath-products/video-capture-cards/visionsc-range/visionsc-s2' => '/product/visionsc-s2',
|
||||
'/datapath-products/video-wall-management-software/wallcontrol-10-software' => '/product/wall-control-10',
|
||||
'/datapath-products/graphics-products/image2k' => '/product/image-2k',
|
||||
'/datapath-products/components-accessories/rack-mounting-kits' => '/products',
|
||||
'/datapath-products/sqx-ip-capture/active-sqx' => '/product/activesqx',
|
||||
'/datapath-products/video-wall-control' => '/products/video-wall-controllers-and-management',
|
||||
'/' . $awardSeg => $awardTarget,
|
||||
];
|
||||
$missing = 0;
|
||||
foreach (array_unique(array_values($mapping2)) as $t) {
|
||||
if (!$targetExists($t)) { echo " !! Ziel fehlt: $t\n"; $missing++; }
|
||||
}
|
||||
if ($missing > 0 && $apply) { exit("Abbruch: Ziele fehlen.\n"); }
|
||||
|
||||
$hostPrefix = '(?:https?://(?:www\.)?datapath(?:-us)?\.(?:co\.uk|com))?';
|
||||
$linkTables = ['tx_news_domain_model_news' => 'bodytext', 'tt_content' => 'bodytext'];
|
||||
$linkHits = 0;
|
||||
foreach ($linkTables as $table => $field) {
|
||||
$select = $pdo->query("SELECT uid, $field AS body FROM $table WHERE deleted = 0 AND ($field LIKE '%datapath%' OR $field LIKE '%/resource-center/%' OR $field LIKE '%/services/%' OR $field LIKE '%/contact-sales%' OR $field LIKE '%/aetria-use-cases/%' OR $field LIKE '%/video-wall-solutions%' OR $field LIKE '%/insights/success-stories%' OR $field LIKE '%/legacy-products/%' OR $field LIKE '%aetria-award-wins%')");
|
||||
$update = $pdo->prepare("UPDATE $table SET $field = ?, tstamp = ? WHERE uid = ?");
|
||||
foreach ($select as $r) {
|
||||
$body = (string)$r['body'];
|
||||
$new = $body;
|
||||
foreach ($mapping2 as $old => $target) {
|
||||
$pattern = '#href="' . $hostPrefix . preg_quote($old, '#') . '/?(?:\?[^"]*)?"#';
|
||||
$new = preg_replace($pattern, 'href="' . $target . '"', $new) ?? $new;
|
||||
}
|
||||
if ($new !== $body) {
|
||||
$linkHits++;
|
||||
echo " $table #{$r['uid']}: Links ersetzt\n";
|
||||
if ($apply) { $update->execute([$new, $now, $r['uid']]); }
|
||||
}
|
||||
}
|
||||
}
|
||||
echo " Records mit ersetzten Links: $linkHits\n";
|
||||
foreach ($mapping2 as $old => $target) {
|
||||
$addRedirect($old, $target);
|
||||
$addRedirect($old . '/', $target);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n6. http://www.vitec.com./ (Punkt-Tippfehler)\n";
|
||||
$n = (int)$pdo->query("SELECT COUNT(*) FROM tx_news_domain_model_news WHERE deleted = 0 AND bodytext LIKE '%www.vitec.com./%'")->fetchColumn();
|
||||
echo " $n Record(s)\n";
|
||||
if ($apply && $n > 0) {
|
||||
$pdo->exec("UPDATE tx_news_domain_model_news SET bodytext = REPLACE(REPLACE(bodytext, 'http://www.vitec.com./', 'https://www.vitec.com/'), 'https://www.vitec.com./', 'https://www.vitec.com/'), tstamp = $now WHERE deleted = 0 AND bodytext LIKE '%www.vitec.com./%'");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n7. Redirects fuer Record-Shells und verwaiste Produkt-URLs\n";
|
||||
foreach ([
|
||||
'/product' => '/products',
|
||||
'/news/article' => '/insights/news',
|
||||
'/success-stories/story' => '/success-stories',
|
||||
'/product/artiosign' => '/products/video-wall-management-software',
|
||||
'/product/artioview' => '/products/video-wall-management-software',
|
||||
'/product/x-view-integrated-displays' => '/products/x-view-integrated-displays',
|
||||
] as $old => $target) {
|
||||
$addRedirect($old, $target);
|
||||
$addRedirect($old . '/', $target);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n8. Meta-Description /datasheets\n";
|
||||
$st = $pdo->prepare("UPDATE pages SET description = ?, tstamp = ? WHERE uid = 17 AND (description IS NULL OR description = '')");
|
||||
$desc = 'Download VITEC and Datapath product datasheets: encoders and decoders, IPTV and digital signage platforms, video wall controllers, capture cards and more - searchable by product and category.';
|
||||
if ($apply) { $st->execute([$desc, $now]); echo " gesetzt (" . $st->rowCount() . ")\n"; } else { echo " wird gesetzt, falls leer\n"; }
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n9. WordPress-Reste in den SEO-Artikeln\n";
|
||||
$seoUids = [232,234,235,236,237,238,240,241,242,243,244,245,246,247,248,249,252];
|
||||
$sel = $pdo->query("SELECT uid, path_segment, bodytext FROM tx_news_domain_model_news WHERE deleted = 0 AND uid IN (" . implode(',', $seoUids) . ")");
|
||||
$upd = $pdo->prepare("UPDATE tx_news_domain_model_news SET bodytext = ?, tstamp = ? WHERE uid = ?");
|
||||
foreach ($sel as $r) {
|
||||
$b = (string)$r['bodytext'];
|
||||
$o = $b;
|
||||
$log = [];
|
||||
$b = preg_replace('#<img[^>]*src="data:image/[^"]*"[^>]*>\s*#i', '', $b, -1, $c1) ?? $b; if ($c1) { $log[] = "$c1 Platzhalter-Bilder"; }
|
||||
$b = preg_replace('#<p>[^<]*getWR360PopupSkin[^<]*</p>\s*#i', '', $b, -1, $c2) ?? $b; if ($c2) { $log[] = "Script-Rest"; }
|
||||
$b = preg_replace('#\s+class="wp-[^"]*"#i', '', $b, -1, $c3) ?? $b; if ($c3) { $log[] = "$c3 wp-Klassen"; }
|
||||
$b = preg_replace('#\s+data-[a-z0-9-]+="[^"]*"#i', '', $b, -1, $c4) ?? $b; if ($c4) { $log[] = "$c4 data-Attribute"; }
|
||||
// bare description text between a product <h3> and the following link
|
||||
$b = preg_replace('#(</h3>)\s*([^<\s][^<]{20,}?)\s*(?=<a |<h[2-4]|<p>|<ul>)#s', '$1<p>$2</p>', $b, -1, $c5) ?? $b; if ($c5) { $log[] = "$c5 nackte Textabsaetze gekapselt"; }
|
||||
$b = preg_replace('#<a([^>]*)>\s*View products?\s*</a>#i', '<a$1>View products</a>', $b, -1, $c6) ?? $b; if ($c6) { $log[] = "$c6 View-products-Links normalisiert"; }
|
||||
if ($b !== $o) {
|
||||
echo " #{$r['uid']} {$r['path_segment']}: " . implode(', ', $log) . "\n";
|
||||
if ($apply) { $upd->execute([$b, $now, $r['uid']]); }
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
echo "\n10. News-Zeiger-Records (type=1) -> Inhalt der alten Seite uebernehmen\n";
|
||||
$allowedTags = '<p><h2><h3><h4><ul><ol><li><strong><em><b><i><a><br><blockquote>';
|
||||
$stripAttrs = static function (string $html): string {
|
||||
// keep href/target on links, drop everything else (style, class, ids)
|
||||
$html = preg_replace_callback('#<a\b([^>]*)>#i', static function (array $m): string {
|
||||
preg_match('/href="([^"]*)"/i', $m[1], $h);
|
||||
preg_match('/target="([^"]*)"/i', $m[1], $t);
|
||||
return '<a' . (isset($h[1]) ? ' href="' . $h[1] . '"' : '') . (isset($t[1]) ? ' target="' . $t[1] . '"' : '') . '>';
|
||||
}, $html) ?? $html;
|
||||
return preg_replace('#<(p|h[2-4]|ul|ol|li|strong|em|b|i|blockquote|br)\b[^>]*>#i', '<$1>', $html) ?? $html;
|
||||
};
|
||||
$collectContent = null;
|
||||
$collectContent = static function (int $pid, int $parent, array &$texts, array &$imageRefs) use ($pdo, &$collectContent, $allowedTags, $stripAttrs): void {
|
||||
$st = $pdo->prepare("SELECT uid, CType, bodytext, header FROM tt_content WHERE pid = ? AND tx_container_parent = ? AND deleted = 0 AND hidden = 0 ORDER BY colPos, sorting");
|
||||
$st->execute([$pid, $parent]);
|
||||
foreach ($st->fetchAll() as $el) {
|
||||
$ctype = (string)$el['CType'];
|
||||
if (str_starts_with($ctype, 'vitec_container') || str_starts_with($ctype, 'vitec_cols')) {
|
||||
$collectContent($pid, (int)$el['uid'], $texts, $imageRefs);
|
||||
continue;
|
||||
}
|
||||
if (in_array($ctype, ['image', 'textpic', 'textmedia'], true)) {
|
||||
$refs = $pdo->prepare("SELECT uid FROM sys_file_reference WHERE tablenames = 'tt_content' AND uid_foreign = ? AND fieldname IN ('image', 'assets', 'media') AND deleted = 0 AND hidden = 0 ORDER BY sorting_foreign");
|
||||
$refs->execute([(int)$el['uid']]);
|
||||
foreach ($refs->fetchAll() as $ref) { $imageRefs[] = (int)$ref['uid']; }
|
||||
}
|
||||
if (in_array($ctype, ['text', 'textpic', 'textmedia'], true)) {
|
||||
$t = trim((string)$el['bodytext']);
|
||||
if ($t !== '') { $texts[] = $stripAttrs(strip_tags($t, $allowedTags)); }
|
||||
continue;
|
||||
}
|
||||
if ($ctype === 'html') {
|
||||
$raw = (string)$el['bodytext'];
|
||||
if (preg_match('#<blockquote[^>]*>(.*?)</blockquote>#is', $raw, $q)) {
|
||||
$cite = '';
|
||||
if (preg_match('#</blockquote>\s*<span[^>]*>(.*?)</span>#is', $raw, $s)) { $cite = trim(strip_tags($s[1])); }
|
||||
$texts[] = '<blockquote><p>' . trim(strip_tags($q[1])) . '</p>' . ($cite !== '' ? '<p><em>' . $cite . '</em></p>' : '') . '</blockquote>';
|
||||
continue;
|
||||
}
|
||||
if (stripos($raw, '<style') !== false || stripos($raw, '<script') !== false || mb_strlen(trim(strip_tags($raw))) < 200) {
|
||||
continue; // layout snippets (buttons, styles)
|
||||
}
|
||||
$texts[] = $stripAttrs(strip_tags($raw, $allowedTags));
|
||||
}
|
||||
}
|
||||
};
|
||||
$pointerRows = $pdo->query("SELECT n.uid, n.pid, n.title, n.path_segment, n.internalurl, n.bodytext, n.fal_media FROM tx_news_domain_model_news n WHERE n.deleted = 0 AND n.type = '1'")->fetchAll();
|
||||
$pageSlugs = $pdo->prepare("SELECT uid, slug, pid FROM pages WHERE uid = ? AND deleted = 0");
|
||||
$maxRefSorting = $pdo->prepare("SELECT COALESCE(MAX(sorting_foreign), 0) FROM sys_file_reference WHERE tablenames = 'tx_news_domain_model_news' AND fieldname = 'fal_media' AND uid_foreign = ?");
|
||||
$copyRef = $pdo->prepare(
|
||||
"INSERT INTO sys_file_reference (pid, tstamp, crdate, uid_local, uid_foreign, tablenames, fieldname, sorting_foreign, title, alternative, description, crop, link)
|
||||
SELECT ?, ?, ?, uid_local, ?, 'tx_news_domain_model_news', 'fal_media', ?, title, alternative, description, crop, link FROM sys_file_reference WHERE uid = ?"
|
||||
);
|
||||
$updNews = $pdo->prepare("UPDATE tx_news_domain_model_news SET bodytext = ?, type = '0', internalurl = '', fal_media = ?, tstamp = ? WHERE uid = ?");
|
||||
$updType = $pdo->prepare("UPDATE tx_news_domain_model_news SET type = '0', internalurl = '', tstamp = ? WHERE uid = ?");
|
||||
$migrated = 0; $typeOnly = 0; $skipped = 0;
|
||||
foreach ($pointerRows as $n) {
|
||||
$pid = 0;
|
||||
if (preg_match('/uid=(\d+)/', (string)$n['internalurl'], $m)) { $pid = (int)$m[1]; } elseif (ctype_digit((string)$n['internalurl'])) { $pid = (int)$n['internalurl']; }
|
||||
$pageSlugs->execute([$pid]);
|
||||
$page = $pageSlugs->fetch();
|
||||
if (!$page || !in_array((int)$page['pid'], [106], true) && (int)$page['uid'] !== 107) {
|
||||
echo " #{$n['uid']}: Ziel-Seite $pid nicht im alten News-Ast - uebersprungen\n";
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
$oldSlug = (string)$page['slug'];
|
||||
$newUrl = '/news/article/' . $n['path_segment'];
|
||||
if (trim((string)$n['bodytext']) !== '') {
|
||||
echo " #{$n['uid']}: hat eigenen Text (" . strlen((string)$n['bodytext']) . " Z.) -> nur type=0" . "\n";
|
||||
$typeOnly++;
|
||||
if ($apply) { $updType->execute([$now, $n['uid']]); }
|
||||
$addRedirect($oldSlug, $newUrl);
|
||||
continue;
|
||||
}
|
||||
$texts = []; $imageRefs = [];
|
||||
$collectContent($pid, 0, $texts, $imageRefs);
|
||||
$body = trim(implode("\n", $texts));
|
||||
$plainLen = mb_strlen(trim(strip_tags($body)));
|
||||
if ($plainLen < 500) {
|
||||
echo " #{$n['uid']}: Seite $pid liefert nur $plainLen Zeichen Text - uebersprungen (bleibt Zeiger)\n";
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
$imageRefs = array_slice(array_unique($imageRefs), 0, 3);
|
||||
// Imported placeholder slugs ("default-<hash>") take the old page's slug.
|
||||
$segment = (string)$n['path_segment'];
|
||||
if (str_starts_with($segment, 'default-')) {
|
||||
$candidate = basename($oldSlug);
|
||||
$newsBySegment->execute([$candidate]);
|
||||
if ((int)$newsBySegment->fetchColumn() === 0) {
|
||||
$segment = $candidate;
|
||||
$newUrl = '/news/article/' . $segment;
|
||||
echo " #{$n['uid']}: Slug default-... -> $segment\n";
|
||||
}
|
||||
}
|
||||
echo " #{$n['uid']} $segment: $plainLen Zeichen aus " . count($texts) . " Elementen, " . count($imageRefs) . " Bild(er) <- $oldSlug\n";
|
||||
$migrated++;
|
||||
if ($apply) {
|
||||
if ($segment !== (string)$n['path_segment']) {
|
||||
$pdo->prepare("UPDATE tx_news_domain_model_news SET path_segment = ? WHERE uid = ?")->execute([$segment, $n['uid']]);
|
||||
}
|
||||
$sorting = 0;
|
||||
$maxRefSorting->execute([$n['uid']]);
|
||||
$sorting = (int)$maxRefSorting->fetchColumn();
|
||||
$existing = (int)$n['fal_media'];
|
||||
foreach ($imageRefs as $refUid) {
|
||||
$sorting += 256;
|
||||
$copyRef->execute([(int)$n['pid'], $now, $now, (int)$n['uid'], $sorting, $refUid]);
|
||||
$existing++;
|
||||
}
|
||||
$updNews->execute([$body, $existing, $now, $n['uid']]);
|
||||
}
|
||||
$addRedirect($oldSlug, $newUrl);
|
||||
}
|
||||
echo " uebernommen: $migrated, nur type=0: $typeOnly, uebersprungen: $skipped\n";
|
||||
|
||||
echo "\nRedirects neu: $redirectsAdded\n";
|
||||
echo $apply ? "FERTIG. cache:flush + SSR purge + warm.\n" : "DRY RUN beendet.\n";
|
||||
Reference in new issue
Block a user