Seobility-Crawl 2: Karten-Links, Platzhalter-News, Titel-Kontext ueber Ordner, Descriptions 155 Zeichen
Migration crawl2_fixes_20261110 (angewendet): sechs Karten-Buttons zeigten auf versteckte/geloeschte Produkt-URLs (ArtioSign/ArtioView -> /products/aetria, Redirects folgen; X-View-Selbstlink entfernt; Chassis-Karte auf das verbliebene Produkt). Drei weitere News trugen noch den InfoComm-Importplatzhalter und doppelten echte Artikel (JODDB, Bluefin, NAB 2026) -> geloescht + 301; EKOM-Artikel bekommt seinen Text von der alten /en/news-Seite; byte-identische Datapath- Kopie (#551) -> 301. Drei Tippfehler. Tote vitec.com-Links in 14 Datensaetzen auf interne Ziele (/markets, /insights/sustainability, Story Northwestern, /product/fx4, 'vitec.com./' und ' '-Links). SEO-Titel fuer sieben Hauptseiten, Descriptions fuer neun Kategorieseiten mit < 60 Zeichen. VitecMetaHandler: Titel-Kontext ueberspringt Ordner-/Shortcut-Ebenen ('End-Points | Products | VITEC' statt 'End-Points | VITEC'); kurze Story-Titel bekommen '| Success Story'; Meta-Description auf 155 Zeichen gekappt, auch redaktionelle (Seobility: 527 'zu lang'). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4b6731786d
commit
e357f42b81
2 files changed
+322
-9
No files matched your search
@@ -0,0 +1,296 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes from the second Seobility crawl of 2026-11-10 (after the duplicate clean-up).
|
||||||
|
*
|
||||||
|
* A. Card CTAs pointing at hidden/deleted product URLs (internal 301 hops):
|
||||||
|
* ArtioSign/ArtioView cards -> /products/aetria (and the two redirects
|
||||||
|
* follow), X-View self-link cleared, chassis card -> surviving product.
|
||||||
|
* B. News shells still carrying the InfoComm import placeholder text:
|
||||||
|
* #6/#7/#5 are duplicates of real articles (#17/#18/#16) -> deleted + 301;
|
||||||
|
* #4 (EKOM) gets its text from the old /en/news page. #551 is a byte-
|
||||||
|
* identical copy of #550 under a title that does not match the text -> 301.
|
||||||
|
* C. Three typos.
|
||||||
|
* D. Dead vitec.com links in old articles: "https://www.vitec.com/ ",
|
||||||
|
* trailing-dot host, /industries, /green-initiative, two dead PDFs ->
|
||||||
|
* internal targets.
|
||||||
|
* E. SEO titles for the short top-level pages (/solutions "Solutions | VITEC" etc.).
|
||||||
|
* F. Meta descriptions for the nine category pages whose generated text was
|
||||||
|
* under 60 characters ("VITEC Avedia: Avedia Platform.").
|
||||||
|
*
|
||||||
|
* Dry run: php migrations/crawl2_fixes_20261110.php
|
||||||
|
* Write: php migrations/crawl2_fixes_20261110.php --apply (--only=A,B / --skip=F)
|
||||||
|
*/
|
||||||
|
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', '1');
|
||||||
|
|
||||||
|
$apply = in_array('--apply', $argv, true);
|
||||||
|
$only = []; $skip = [];
|
||||||
|
foreach ($argv as $arg) {
|
||||||
|
if (str_starts_with($arg, '--only=')) { $only = array_map('trim', explode(',', strtoupper(substr($arg, 7)))); }
|
||||||
|
if (str_starts_with($arg, '--skip=')) { $skip = array_map('trim', explode(',', strtoupper(substr($arg, 7)))); }
|
||||||
|
}
|
||||||
|
$runs = static fn(string $s): bool => (!$only || in_array($s, $only, true)) && !in_array($s, $skip, 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();
|
||||||
|
$purgePaths = [];
|
||||||
|
$plain = static fn(?string $h): string => trim((string)preg_replace('/\s+/', ' ', html_entity_decode(strip_tags((string)$h), ENT_QUOTES, 'UTF-8')));
|
||||||
|
|
||||||
|
// --- helpers -----------------------------------------------------------------
|
||||||
|
|
||||||
|
$hasRedirect = $pdo->prepare("SELECT uid, target FROM sys_redirect WHERE deleted = 0 AND source_path = ? LIMIT 1");
|
||||||
|
$insertRedirect = $pdo->prepare(
|
||||||
|
"INSERT INTO sys_redirect (pid, createdon, updatedon, source_host, source_path, target, target_statuscode)
|
||||||
|
VALUES (0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '*', ?, ?, 301)"
|
||||||
|
);
|
||||||
|
$addRedirect = static function (string $source, string $target) use ($pdo, $hasRedirect, $insertRedirect, $apply, &$purgePaths): void {
|
||||||
|
$purgePaths[$source] = true;
|
||||||
|
$hasRedirect->execute([$source]);
|
||||||
|
$existing = $hasRedirect->fetch();
|
||||||
|
if ($existing) {
|
||||||
|
if ($existing['target'] === $target) { return; }
|
||||||
|
echo " redirect $source: Ziel {$existing['target']} -> $target\n";
|
||||||
|
if ($apply) { $pdo->prepare("UPDATE sys_redirect SET target = ?, updatedon = UNIX_TIMESTAMP() WHERE uid = ?")->execute([$target, (int)$existing['uid']]); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
echo " redirect $source -> $target\n";
|
||||||
|
if ($apply) { $insertRedirect->execute([$source, $target]); }
|
||||||
|
};
|
||||||
|
$retarget = static function (string $oldTarget, string $newTarget) use ($pdo, $apply): void {
|
||||||
|
$st = $pdo->prepare("SELECT uid, source_path FROM sys_redirect WHERE deleted = 0 AND target = ?");
|
||||||
|
$st->execute([$oldTarget]);
|
||||||
|
foreach ($st->fetchAll() as $r) {
|
||||||
|
echo " redirect #{$r['uid']} {$r['source_path']}: Ziel $oldTarget -> $newTarget\n";
|
||||||
|
if ($apply) { $pdo->prepare("UPDATE sys_redirect SET target = ?, updatedon = UNIX_TIMESTAMP() WHERE uid = ?")->execute([$newTarget, (int)$r['uid']]); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$softDelete = static function (string $table, int $uid) use ($pdo, $apply, $now): void {
|
||||||
|
echo " loeschen $table #$uid\n";
|
||||||
|
if ($apply) {
|
||||||
|
$pdo->prepare("UPDATE $table SET deleted = 1, tstamp = ? WHERE uid = ?")->execute([$now, $uid]);
|
||||||
|
$pdo->prepare("DELETE FROM sys_category_record_mm WHERE tablenames = ? AND uid_foreign = ?")->execute([$table, $uid]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$newsRow = static function (int $uid) use ($pdo): ?array {
|
||||||
|
$st = $pdo->prepare("SELECT uid, title, path_segment, type, LENGTH(bodytext) AS bl FROM tx_news_domain_model_news WHERE deleted = 0 AND uid = ?");
|
||||||
|
$st->execute([$uid]);
|
||||||
|
$r = $st->fetch();
|
||||||
|
return $r ?: null;
|
||||||
|
};
|
||||||
|
$updateField = static function (string $table, int $uid, string $field, string $value) use ($pdo, $apply, $now): void {
|
||||||
|
if ($apply) { $pdo->prepare("UPDATE $table SET $field = ?, tstamp = ? WHERE uid = ?")->execute([$value, $now, $uid]); }
|
||||||
|
};
|
||||||
|
// Replace patterns in a text field of every matching row; prints each change.
|
||||||
|
$replaceIn = static function (string $table, string $field, array $pairs, string $label) use ($pdo, $apply, $now, &$purgePaths): int {
|
||||||
|
// pair = [needle, replacement, isRegex, likeNeedle (plain text for the SQL prefilter; required for regex pairs)]
|
||||||
|
$like = implode(' OR ', array_fill(0, count($pairs), "$field LIKE ?"));
|
||||||
|
$params = array_map(fn($p) => '%' . ($p[3] ?? $p[0]) . '%', $pairs);
|
||||||
|
$extra = $table === 'tx_news_domain_model_news' ? ', path_segment AS ref' : ($table === 'tt_content' ? ', pid AS ref' : ', uid AS ref');
|
||||||
|
$st = $pdo->prepare("SELECT uid, $field AS v $extra FROM $table WHERE deleted = 0 AND ($like)");
|
||||||
|
$st->execute($params);
|
||||||
|
$n = 0;
|
||||||
|
foreach ($st->fetchAll() as $r) {
|
||||||
|
$new = (string)$r['v'];
|
||||||
|
foreach ($pairs as [$needle, $replacement, $isRegex]) {
|
||||||
|
$new = $isRegex ? (string)preg_replace($needle, $replacement, $new) : str_replace($needle, $replacement, $new);
|
||||||
|
}
|
||||||
|
if ($new === (string)$r['v']) { continue; }
|
||||||
|
$n++;
|
||||||
|
echo " $label: $table #{$r['uid']} ({$r['ref']})\n";
|
||||||
|
if ($table === 'tx_news_domain_model_news') { $purgePaths['/news/article/' . $r['ref']] = true; }
|
||||||
|
if ($apply) { $pdo->prepare("UPDATE $table SET $field = ?, tstamp = ? WHERE uid = ?")->execute([$new, $now, (int)$r['uid']]); }
|
||||||
|
}
|
||||||
|
return $n;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- A. card CTAs onto hidden/deleted products -----------------------------------
|
||||||
|
|
||||||
|
if ($runs('A')) {
|
||||||
|
echo "\nA. Karten-Buttons auf versteckte/geloeschte Produkt-URLs\n";
|
||||||
|
$ctas = [
|
||||||
|
11744 => ['/product/artiosign', '/products/aetria'],
|
||||||
|
11745 => ['/product/artioview', '/products/aetria'],
|
||||||
|
11649 => ['/product/artiosign', '/products/aetria'],
|
||||||
|
11650 => ['/product/artioview', '/products/aetria'],
|
||||||
|
12179 => ['/product/avedia-modular-chassis-c1101-c1103-c1210-1', '/product/avedia-modular-chassis-c1101-c1103-c1210'],
|
||||||
|
];
|
||||||
|
$ce = $pdo->prepare("SELECT uid, pid, header, vitec_primary_cta, vitec_primary_cta_label FROM tt_content WHERE deleted = 0 AND uid = ?");
|
||||||
|
foreach ($ctas as $uid => [$old, $new]) {
|
||||||
|
$ce->execute([$uid]); $c = $ce->fetch();
|
||||||
|
if (!$c) { continue; }
|
||||||
|
if (trim((string)$c['vitec_primary_cta']) !== $old) { echo " CE #$uid: CTA ist '{$c['vitec_primary_cta']}', erwartet '$old' - uebersprungen\n"; continue; }
|
||||||
|
echo " CE #$uid (Seite {$c['pid']}, \"{$c['header']}\"): $old -> $new\n";
|
||||||
|
$updateField('tt_content', $uid, 'vitec_primary_cta', $new);
|
||||||
|
$pageSlug = $pdo->query("SELECT slug FROM pages WHERE uid = {$c['pid']}")->fetchColumn();
|
||||||
|
if ($pageSlug) { $purgePaths[$pageSlug] = true; }
|
||||||
|
}
|
||||||
|
// X-View card links to the page it sits on: no button.
|
||||||
|
$ce->execute([12133]); $c = $ce->fetch();
|
||||||
|
if ($c && trim((string)$c['vitec_primary_cta']) === '/product/x-view-integrated-displays') {
|
||||||
|
echo " CE #12133 (\"{$c['header']}\"): Selbst-Link entfernt (Button weg)\n";
|
||||||
|
$updateField('tt_content', 12133, 'vitec_primary_cta', '');
|
||||||
|
$updateField('tt_content', 12133, 'vitec_primary_cta_label', '');
|
||||||
|
$purgePaths['/products/x-view-integrated-displays'] = true;
|
||||||
|
}
|
||||||
|
// The two product redirects follow the cards.
|
||||||
|
$addRedirect('/product/artiosign', '/products/aetria');
|
||||||
|
$addRedirect('/product/artioview', '/products/aetria');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- B. placeholder news shells -----------------------------------------------------
|
||||||
|
|
||||||
|
if ($runs('B')) {
|
||||||
|
echo "\nB. Platzhalter-News (InfoComm-Importtext)\n";
|
||||||
|
$placeholderMarker = 'VITEC will demonstrate its latest enterprise video technologies at InfoComm 2026';
|
||||||
|
$isPlaceholder = static function (int $uid) use ($pdo, $placeholderMarker): bool {
|
||||||
|
$b = (string)$pdo->query("SELECT bodytext FROM tx_news_domain_model_news WHERE uid = $uid")->fetchColumn();
|
||||||
|
return strlen($b) === 1308 && str_contains($b, $placeholderMarker);
|
||||||
|
};
|
||||||
|
// duplicate shell => real article
|
||||||
|
foreach ([6 => 17, 7 => 18, 5 => 16] as $shell => $real) {
|
||||||
|
$s = $newsRow($shell); $r = $newsRow($real);
|
||||||
|
if (!$s || !$r) { echo " #$shell/#$real fehlt, uebersprungen\n"; continue; }
|
||||||
|
if (!$isPlaceholder($shell)) { echo " #$shell ist kein Platzhalter mehr, uebersprungen\n"; continue; }
|
||||||
|
echo " #$shell \"{$s['title']}\" (Platzhalter) -> #$real \"{$r['title']}\" ({$r['bl']} Zeichen)\n";
|
||||||
|
$softDelete('tx_news_domain_model_news', $shell);
|
||||||
|
$addRedirect('/news/article/' . $s['path_segment'], '/news/article/' . $r['path_segment']);
|
||||||
|
$retarget('/news/article/' . $s['path_segment'], '/news/article/' . $r['path_segment']);
|
||||||
|
$purgePaths['/news/article/' . $r['path_segment']] = true;
|
||||||
|
}
|
||||||
|
// EKOM: text lives on the old page
|
||||||
|
$s = $newsRow(4);
|
||||||
|
if ($s && $isPlaceholder(4)) {
|
||||||
|
$page = $pdo->query("SELECT uid, slug FROM pages WHERE deleted = 0 AND slug LIKE '%/news/vitec-partners-with-ekom%' ORDER BY uid LIMIT 1")->fetch();
|
||||||
|
$html = '';
|
||||||
|
if ($page) {
|
||||||
|
foreach ($pdo->query("SELECT CType, bodytext FROM tt_content WHERE deleted = 0 AND hidden = 0 AND pid = {$page['uid']} AND CType IN ('text','textmedia','html') AND bodytext <> '' ORDER BY sorting") as $c) {
|
||||||
|
$part = (string)$c['bodytext'];
|
||||||
|
if (str_contains($part, 'vitec.com/ ')) { $part = str_replace('https://www.vitec.com/ ', '<a href="/">www.vitec.com</a>', $part); }
|
||||||
|
$html .= $part . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$len = mb_strlen($plain($html));
|
||||||
|
if ($len >= 500) {
|
||||||
|
echo " #4 \"{$s['title']}\": Text von Seite #{$page['uid']} ({$page['slug']}) uebernommen, $len Zeichen\n";
|
||||||
|
$updateField('tx_news_domain_model_news', 4, 'bodytext', trim($html));
|
||||||
|
$purgePaths['/news/article/' . $s['path_segment']] = true;
|
||||||
|
} else {
|
||||||
|
echo " #4: keine alte Seite mit Text gefunden ($len Zeichen) - bleibt\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// byte-identical Datapath pair: keep the one whose title matches the text
|
||||||
|
$a = $newsRow(551); $b = $newsRow(550);
|
||||||
|
if ($a && $b) {
|
||||||
|
$ba = $pdo->query("SELECT bodytext FROM tx_news_domain_model_news WHERE uid = 551")->fetchColumn();
|
||||||
|
$bb = $pdo->query("SELECT bodytext FROM tx_news_domain_model_news WHERE uid = 550")->fetchColumn();
|
||||||
|
if ($ba === $bb) {
|
||||||
|
echo " #551 \"{$a['title']}\" = Textkopie von #550 \"{$b['title']}\" -> weg\n";
|
||||||
|
$softDelete('tx_news_domain_model_news', 551);
|
||||||
|
$addRedirect('/news/article/' . $a['path_segment'], '/news/article/' . $b['path_segment']);
|
||||||
|
$retarget('/news/article/' . $a['path_segment'], '/news/article/' . $b['path_segment']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- C. typos ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if ($runs('C')) {
|
||||||
|
echo "\nC. Tippfehler\n";
|
||||||
|
$replaceIn('tx_news_domain_model_news', 'bodytext', [
|
||||||
|
['/\bsponser\b/', 'sponsor', true, 'sponser'],
|
||||||
|
['/\bSponser\b/', 'Sponsor', true, 'Sponser'],
|
||||||
|
['/\breconnaisance\b/', 'reconnaissance', true, 'reconnaisance'],
|
||||||
|
['/\bReconnaisance\b/', 'Reconnaissance', true, 'Reconnaisance'],
|
||||||
|
['/\bauxillary\b/', 'auxiliary', true, 'auxillary'],
|
||||||
|
['/\bAuxillary\b/', 'Auxiliary', true, 'Auxillary'],
|
||||||
|
], 'Tippfehler');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- D. dead vitec.com links -----------------------------------------------------------
|
||||||
|
|
||||||
|
if ($runs('D')) {
|
||||||
|
echo "\nD. Tote vitec.com-Links\n";
|
||||||
|
$pairs = [
|
||||||
|
['https://www.vitec.com/ ', '<a href="/">www.vitec.com</a>', false],
|
||||||
|
['~https?://www\.vitec\.com\.(?=[/"#\s<])/?~', '/', true, 'www.vitec.com.'],
|
||||||
|
['https://www.vitec.com/green-initiative', '/insights/sustainability', false],
|
||||||
|
['https://www.vitec.com/industries/military/government', '/markets/defense-and-security', false],
|
||||||
|
['https://www.vitec.com/industries', '/markets', false],
|
||||||
|
['~https?://www\.vitec\.com/fileadmin/downloads/Company_Downloads/Use_Cases/Northwestern_University_USE_CASE_WebVersion\.pdf~', '/success-stories/northwestern-university', true, 'Northwestern_University_USE_CASE'],
|
||||||
|
['~https?://datapathdocuments\.co\.uk/wp-content/uploads/Datapath-Fx4_datasheet_EN\.pdf~', '/product/fx4', true, 'Datapath-Fx4_datasheet'],
|
||||||
|
];
|
||||||
|
$n = $replaceIn('tx_news_domain_model_news', 'bodytext', $pairs, 'Link');
|
||||||
|
$n += $replaceIn('tt_content', 'bodytext', $pairs, 'Link');
|
||||||
|
echo " $n Datensaetze\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- E. SEO titles for short top-level pages -------------------------------------------
|
||||||
|
|
||||||
|
if ($runs('E')) {
|
||||||
|
echo "\nE. Seitentitel der Hauptseiten\n";
|
||||||
|
$titles = [
|
||||||
|
'/solutions' => 'Video Solutions for Control Rooms, Streaming & Signage',
|
||||||
|
'/products' => 'Products: IPTV, Video Streaming, Video Walls & Capture',
|
||||||
|
'/support' => 'Support, Training & Product Resources',
|
||||||
|
'/datasheets' => 'Datasheets: Encoders, IPTV, Video Walls & Capture Cards',
|
||||||
|
'/contact' => 'Contact VITEC: Sales, Support & Offices Worldwide',
|
||||||
|
'/contact/locations' => 'VITEC Offices & Locations Worldwide',
|
||||||
|
'/success-stories' => 'Success Stories from Broadcast, Defense & Enterprise',
|
||||||
|
];
|
||||||
|
$st = $pdo->prepare("SELECT uid, title, seo_title FROM pages WHERE deleted = 0 AND slug = ?");
|
||||||
|
foreach ($titles as $slug => $seo) {
|
||||||
|
$st->execute([$slug]); $p = $st->fetch();
|
||||||
|
if (!$p) { continue; }
|
||||||
|
if ($p['seo_title'] === $seo) { continue; }
|
||||||
|
printf(" %-20s \"%s\" -> \"%s\"\n", $slug, $p['seo_title'], $seo);
|
||||||
|
$updateField('pages', (int)$p['uid'], 'seo_title', $seo);
|
||||||
|
$purgePaths[$slug] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- F. descriptions for the thinnest category pages -------------------------------------
|
||||||
|
|
||||||
|
if ($runs('F')) {
|
||||||
|
echo "\nF. Meta-Descriptions der kuerzesten Kategorieseiten\n";
|
||||||
|
$desc = [
|
||||||
|
'/products/ez-tv' => 'EZ TV Platform by VITEC: enterprise IPTV and digital signage for live TV, video distribution and targeted content across screens and devices.',
|
||||||
|
'/products/activesqx' => 'ActiveSQX2 by VITEC and Datapath: IP stream decoding card that brings network video sources onto video walls. Specifications, features and datasheets.',
|
||||||
|
'/products/avedia' => 'Avedia Platform by VITEC: one platform for IP video, digital signage and interactive experiences across enterprise networks.',
|
||||||
|
'/products/rf-gateway' => 'VITEC RF gateways for the Avedia Modular System: receive satellite, cable and terrestrial TV and distribute it as IPTV across the network.',
|
||||||
|
'/products/prism-transcoder' => 'PRISM and PRISM SFF by VITEC: broadcast-grade video transcoders for converting, scaling and re-streaming live video, including a compact small-form-factor model.',
|
||||||
|
'/products/channellink-ip-gateway' => 'ChannelLink IP Gateway by VITEC: brings IP video streams into IPTV and digital signage networks, with transcoding for reliable delivery to every screen.',
|
||||||
|
'/products/aligo' => 'Aligo by VITEC: AV over IP and KVM encoders, receivers and workstations (QTX100, TX100, RX100, TX100 SQX) for control rooms and multi-display environments.',
|
||||||
|
'/products/vms-plugins-and-integrations' => 'VMS plugins and integrations for VITEC and Datapath video walls: bring Genetec and Milestone video management systems onto operator displays.',
|
||||||
|
'/products/image-graphics-cards' => 'Datapath Image graphics cards from VITEC (Image 2K, Image 4K, IQS4): multi-output graphics for video walls and multi-display workstations.',
|
||||||
|
];
|
||||||
|
$st = $pdo->prepare("SELECT uid, description FROM pages WHERE deleted = 0 AND slug = ?");
|
||||||
|
foreach ($desc as $slug => $d) {
|
||||||
|
$st->execute([$slug]); $p = $st->fetch();
|
||||||
|
if (!$p || $p['description'] === $d) { continue; }
|
||||||
|
printf(" %-42s (%d -> %d Zeichen)\n alt: %s\n neu: %s\n", $slug, strlen((string)$p['description']), strlen($d), $p['description'], $d);
|
||||||
|
$updateField('pages', (int)$p['uid'], 'description', $d);
|
||||||
|
$purgePaths[$slug] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\nSSR-Pfade zum Purgen (" . count($purgePaths) . "):\n " . implode("\n ", array_keys($purgePaths)) . "\n";
|
||||||
|
echo $apply ? "\nFERTIG.\n" : "\n(Trockenlauf, nichts geschrieben)\n";
|
||||||
@@ -123,6 +123,11 @@ class VitecMetaHandler extends MetaHandler
|
|||||||
if ($recordTitle !== '') {
|
if ($recordTitle !== '') {
|
||||||
$title = $recordTitle;
|
$title = $recordTitle;
|
||||||
}
|
}
|
||||||
|
// "Betfred HQ | VITEC" is too short to say what the page is.
|
||||||
|
$storyCore = trim((string)preg_replace('/\s*[|\x{2013}\-]\s*vitec\s*$/iu', '', $title));
|
||||||
|
if ($storyCore !== '' && mb_strlen($storyCore) < 30 && mb_stripos($storyCore, 'success story') === false) {
|
||||||
|
$title = $storyCore . ' | Success Story';
|
||||||
|
}
|
||||||
$description = trim((string)($usecase['seo_description'] ?? ''));
|
$description = trim((string)($usecase['seo_description'] ?? ''));
|
||||||
if ($description === '') {
|
if ($description === '') {
|
||||||
$description = trim((string)($usecase['og_description'] ?? ''));
|
$description = trim((string)($usecase['og_description'] ?? ''));
|
||||||
@@ -182,12 +187,19 @@ class VitecMetaHandler extends MetaHandler
|
|||||||
|
|
||||||
// --- compose meta list ------------------------------------------------
|
// --- compose meta list ------------------------------------------------
|
||||||
$meta = is_array($content['seo']['meta'] ?? null) ? $content['seo']['meta'] : [];
|
$meta = is_array($content['seo']['meta'] ?? null) ? $content['seo']['meta'] : [];
|
||||||
$description = $this->crop($description, 300);
|
// ~155 characters is what search engines show (Seobility: 527 "zu lang").
|
||||||
|
$description = $this->crop($description, 155);
|
||||||
$absImage = $image !== '' ? $this->absolute($image, $base) : '';
|
$absImage = $image !== '' ? $this->absolute($image, $base) : '';
|
||||||
|
|
||||||
if ($description !== '' && !$this->hasMeta($meta, 'name', 'description')) {
|
if ($description !== '' && !$this->hasMeta($meta, 'name', 'description')) {
|
||||||
$meta[] = ['name' => 'description', 'content' => $description];
|
$meta[] = ['name' => 'description', 'content' => $description];
|
||||||
}
|
}
|
||||||
|
// Editorial descriptions already in the list get the same length cap.
|
||||||
|
foreach ($meta as $index => $entry) {
|
||||||
|
if (($entry['name'] ?? '') === 'description' && is_string($entry['content'] ?? null)) {
|
||||||
|
$meta[$index]['content'] = $this->crop(trim($entry['content']), 155);
|
||||||
|
}
|
||||||
|
}
|
||||||
$finalDescription = $this->metaContent($meta, 'name', 'description');
|
$finalDescription = $this->metaContent($meta, 'name', 'description');
|
||||||
if ($finalDescription === '') {
|
if ($finalDescription === '') {
|
||||||
$finalDescription = $description;
|
$finalDescription = $description;
|
||||||
@@ -267,23 +279,28 @@ class VitecMetaHandler extends MetaHandler
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
// Rootline keys are the depth (0 = root), but the array is krsort()ed
|
// Rootline keys are the depth (0 = root), but the array is krsort()ed
|
||||||
// (current page first). Sort by depth and take the entry before last.
|
// (current page first). Walk up from the parent, skipping structural
|
||||||
|
// levels (folders, shortcuts, spacers, technical names) until a page
|
||||||
|
// with a presentable name turns up; the site root never counts.
|
||||||
$rootLine = $pageInfo->getRootLine();
|
$rootLine = $pageInfo->getRootLine();
|
||||||
ksort($rootLine);
|
ksort($rootLine);
|
||||||
$byDepth = array_values($rootLine);
|
$byDepth = array_values($rootLine);
|
||||||
$parent = count($byDepth) >= 3 ? ($byDepth[count($byDepth) - 2] ?? null) : null;
|
for ($i = count($byDepth) - 2; $i >= 1; $i--) {
|
||||||
if (!is_array($parent)) {
|
$candidate = $byDepth[$i];
|
||||||
return '';
|
if (!is_array($candidate)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (in_array((int)($parent['doktype'] ?? 1), [3, 4, 199, 254, 255], true)) {
|
if (in_array((int)($candidate['doktype'] ?? 1), [3, 4, 199, 254, 255], true)) {
|
||||||
return '';
|
continue;
|
||||||
}
|
}
|
||||||
$name = trim((string)($parent['nav_title'] ?? '')) ?: trim((string)($parent['title'] ?? ''));
|
$name = trim((string)($candidate['nav_title'] ?? '')) ?: trim((string)($candidate['title'] ?? ''));
|
||||||
if ($name === '' || preg_match('/^user[_ -]?nav$/i', $name) === 1 || str_contains($name, '_')) {
|
if ($name === '' || preg_match('/^user[_ -]?nav$/i', $name) === 1 || str_contains($name, '_')) {
|
||||||
return '';
|
continue;
|
||||||
}
|
}
|
||||||
return $name;
|
return $name;
|
||||||
}
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route arguments of the current request (record detail context).
|
* Route arguments of the current request (record detail context).
|
||||||
|
|||||||
Reference in new issue
Block a user