diff --git a/migrations/news_story_canonical_20261110.php b/migrations/news_story_canonical_20261110.php new file mode 100644 index 0000000..786a41b --- /dev/null +++ b/migrations/news_story_canonical_20261110.php @@ -0,0 +1,74 @@ + PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC] +); + +echo $apply ? "== APPLY ==\n" : "== DRY RUN (add --apply to write) ==\n"; + +// news uid => usecase (story) uid +$pairs = [ + 361 => 56, // Denver Sportsbook Bar & Grill + 121 => 24, // European Space Agency + 164 => 26, // Feature Story News + 336 => 62, // Kyndryl Cyber Operations Center + 388 => 74, // Leading Video Capture for the Medical Market + 239 => 66, // RRS Sir David Attenborough + 158 => 46, // Sacramento Kings + 310 => 68, // SENTRY Video Walls – 2024 Olympic Games + 290 => 72, // UK Mission Critical Control Room + 389 => 59, // Intercity Istanbul Park + 436 => 64, // NHS tribute onto Parliament + 395 => 54, // Bahrain International Circuit + 327 => 60, // ITV News Headquarters +]; + +$news = $pdo->prepare("SELECT uid, title, path_segment, vitec_canonical_usecase FROM tx_news_domain_model_news WHERE deleted = 0 AND uid = ?"); +$story = $pdo->prepare("SELECT uid, title, slug, hidden FROM tx_vitec_domain_model_usecase WHERE deleted = 0 AND uid = ?"); +$update = $pdo->prepare("UPDATE tx_news_domain_model_news SET vitec_canonical_usecase = ?, tstamp = UNIX_TIMESTAMP() WHERE uid = ?"); +$set = 0; +$paths = []; +foreach ($pairs as $newsUid => $storyUid) { + $news->execute([$newsUid]); $n = $news->fetch(); + $story->execute([$storyUid]); $s = $story->fetch(); + if (!$n || !$s) { echo " #$newsUid -> #$storyUid: Datensatz fehlt, uebersprungen\n"; continue; } + if ((int)$s['hidden'] === 1) { echo " #$newsUid -> #$storyUid: Story versteckt, uebersprungen\n"; continue; } + $state = (int)$n['vitec_canonical_usecase'] === $storyUid ? 'schon gesetzt' : 'setzen'; + printf(" news #%-4d %-70s -> story #%-3d /success-stories/%s [%s]\n", $newsUid, mb_substr($n['title'], 0, 70), $storyUid, $s['slug'], $state); + $paths[] = '/news/article/' . $n['path_segment']; + if ($state === 'setzen') { + $set++; + if ($apply) { $update->execute([$storyUid, $newsUid]); } + } +} +echo "\n$set Zuordnung(en) " . ($apply ? 'geschrieben' : 'wuerden geschrieben') . ".\n"; +echo "SSR-Pfade:\n " . implode("\n ", $paths) . "\n"; diff --git a/packages/vitec/Classes/Seo/VitecMetaHandler.php b/packages/vitec/Classes/Seo/VitecMetaHandler.php index ea3642e..eaeca12 100644 --- a/packages/vitec/Classes/Seo/VitecMetaHandler.php +++ b/packages/vitec/Classes/Seo/VitecMetaHandler.php @@ -67,6 +67,7 @@ class VitecMetaHandler extends MetaHandler $title = trim((string)($content['seo']['title'] ?? '')); $description = ''; $image = ''; + $canonicalPath = ''; // --- record context ------------------------------------------------- $newsUid = (int)($args['tx_news_pi1']['news'] ?? 0); @@ -90,6 +91,15 @@ class VitecMetaHandler extends MetaHandler ); } $image = $this->firstImage('tx_news_domain_model_news', 'fal_media', $newsUid); + // Same text as a success story: the story is the original. + $storyUid = (int)($news['vitec_canonical_usecase'] ?? 0); + if ($storyUid > 0) { + $story = $this->fetchRow('tx_vitec_domain_model_usecase', ['uid' => $storyUid]); + $storySlug = trim((string)($story['slug'] ?? '')); + if ($storySlug !== '') { + $canonicalPath = '/success-stories/' . $storySlug; + } + } } } elseif ($productArg !== '') { $product = is_numeric($productArg) @@ -227,6 +237,23 @@ class VitecMetaHandler extends MetaHandler } } + // --- news article whose canonical is a success story -------------------- + if ($canonicalPath !== '') { + $href = $this->absolute($canonicalPath, $base); + $links = is_array($content['seo']['link'] ?? null) ? $content['seo']['link'] : []; + $replaced = false; + foreach ($links as $index => $link) { + if (($link['rel'] ?? '') === 'canonical') { + $links[$index]['href'] = $href; + $replaced = true; + } + } + if (!$replaced) { + $links[] = ['href' => $href, 'rel' => 'canonical']; + } + $content['seo']['link'] = $links; + } + return $content; } diff --git a/packages/vitec/Configuration/TCA/Overrides/tx_news_domain_model_news.php b/packages/vitec/Configuration/TCA/Overrides/tx_news_domain_model_news.php new file mode 100644 index 0000000..925d7c5 --- /dev/null +++ b/packages/vitec/Configuration/TCA/Overrides/tx_news_domain_model_news.php @@ -0,0 +1,30 @@ + [ + 'label' => 'Canonical success story', + 'description' => 'Set when a success story carries the same text as this article. The article stays in the news list; search engines count the story as the original.', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_vitec_domain_model_usecase', + 'foreign_table_where' => 'AND {#tx_vitec_domain_model_usecase}.{#deleted} = 0 ORDER BY tx_vitec_domain_model_usecase.title', + 'items' => [['label' => '— none —', 'value' => 0]], + 'default' => 0, + 'size' => 1, + 'maxitems' => 1, + ], + ], + ]); + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( + 'tx_news_domain_model_news', + '--div--;VITEC SEO, vitec_canonical_usecase' + ); +})(); diff --git a/packages/vitec/ext_tables.sql b/packages/vitec/ext_tables.sql index da33ef7..6fbc2e6 100755 --- a/packages/vitec/ext_tables.sql +++ b/packages/vitec/ext_tables.sql @@ -518,3 +518,7 @@ CREATE TABLE tx_vitec_clienterrorlog ( PRIMARY KEY (uid), KEY crdate (crdate) ); + +CREATE TABLE tx_news_domain_model_news ( + vitec_canonical_usecase int(11) unsigned DEFAULT '0' NOT NULL +);