Bitte beachte, dass sich diese Dokumentation auf die neuste Version dieser Erweiterung bezieht. Wenn eine ältere Version eingesetzt wird, kann diese abweichen. Die jeweils passende Dokumentation befindet sich im Dokumentation-Verzeichnis der Erweiterung.
Wie baue ich ein Glossar mit alphabetischer Gruppierung?
Du möchtest eine A–Z-Glossar-Navigation über Kategorien erstellen, dabei sprechende URLs nach /buchstabe/begriff und eine korrekte sitemap.xml erzeugen, obwohl Dein Glossar die Kategorie per N:M (MM-Tabelle) speichert? Dann bist Du hier genau richtig!
Als Ergebnis möchten wir ein Glossar wie auf unserer Website erhalten:
Die URLs in der Sitemap.xml sollen wie folgt aussehen:
- https://www.coding.ms/glossary-entry/a/api
- https://www.coding.ms/glossary-entry/b/bcc
- https://www.coding.ms/glossary-entry/c/csv
- https://www.coding.ms/glossary-entry/f/fal
- …
Voraussetzungen
- TYPO3 in Version 11–13
- EXT:seo muss für die sprechenden URLs installiert sein
- EXT:glossaries für Extbase-Plugin der Liste + Details
Achtung:
Wenn ein Glossar-Eintrag mehrere Kategorien hat, ist
/buchstabe/begriffmehrdeutig. Für A–Z solltest du genau 1 Kategorie je Eintrag erzwingen (TCA/Editor-UX), sonst wird Sitemap/Linkbuilding inkonsistent.
Alphabet-Reiter: Einträge sauber Buchstaben zuordnen
Vorgehen
- Lege 26 Kategorien
A…Zan (Titel +slug=a…z) - Jeder Glossar-Eintrag bekommt genau eine dieser Kategorien (sein Anfangsbuchstabe)
TCA: „Genau eine Kategorie“ erzwingen (empfohlen)
Setze in deinem Site-Package in der Datei Configuration/TCA/Overrides/tx_glossaries_domain_model_glossary.php
// Reduce assigned categories to exactly one!
$GLOBALS['TCA']['tx_glossaries_domain_model_glossary']['columns']['categories']['config']['maxitems'] = 1;
Achtung:
Nach der Anpassung nicht vergessen den System-Cache zu leeren!
URL-Aufbau via RouteEnhancer
Ziel
Ziel ist es eine URL wie /category_slug/glossary_slug für die Detailseite zu haben.
- Liste:
.../glossary - Detail:
.../glossary/r/rte
Beispiel: Site-Config RouteEnhancer
config/sites/<siteIdentifier>/config.yaml
routeEnhancers:
GlossariesPlugin:
type: Extbase
limitToPages:
- 123
extension: Glossaries
plugin: Glossary
routes:
-
routePath: '/{category_slug}'
_controller: 'Glossary::list'
_arguments:
category_slug: selectedCategory
-
routePath: '/{category_slug}/{glossary_slug}'
_controller: 'Glossary::show'
_arguments:
category_slug: selectedCategory
glossary_slug: glossary
defaultController: 'Glossary::list'
aspects:
category_slug:
type: PersistedAliasMapper
tableName: tx_glossaries_domain_model_glossarycategory
routeFieldName: slug
routeValuePrefix: /
glossary_slug:
type: PersistedAliasMapper
tableName: tx_glossaries_domain_model_glossary
routeFieldName: slug
routeValuePrefix: /
Hinweis:
Das funktioniert nur stabil, wenn
selectedCategorywirklich die UID der zugeordneten Kategorie enthält (oder per Aspect sauber auflösbar ist). Genau da hakt die Standard-Sitemap bei MM-Feldern oft.
Warum zeigt die sitemap.xml immer a als Kategorie in der URL?
Der Standard-Provider TYPO3\CMS\Seo\XmlSitemap\RecordsXmlSitemapDataProvider befüllt fieldToParameterMap bei N:M-Feldern nicht zuverlässig, weil im Datensatzfeld (categories) nicht automatisch die konkrete, sortierte Relation aus der MM-Tabelle aufgelöst wird.
Effekt: In fieldToParameterMap wird die Anzahl der zugewiesenen Relationen der N:M-Relation verwendet, welcher in unserem Fall 1 ist, weil wir ja nur genau eine Kategorie zulassen. Die Kategorie mit der UID 1 ist in unserem Beispiel zufällig Kategorie A – daher generiert die Sitemap immer URLs mit /a/....
Lösung: Custom SitemapDataProvider für die „erste“ Kategorie aus der MM-Tabelle
Prinzip
-
Vor dem URL-Build:
- erste Kategorie-UID aus der MM-Tabelle holen (nach
sorting_foreign) $record['categories'] = <uid>setzen
- erste Kategorie-UID aus der MM-Tabelle holen (nach
-
Dann den Standard-Mechanismus weiter nutzen, damit dein
fieldToParameterMap+ RouteEnhancer greifen
Beispiel-Implementierung (erweiterter Records-Provider)
Dieser XmlSitemap-DataProvider wird auch in der EXT:glossaries in Classes/XmlSitemap/RecordsXmlSitemapDataProvider.php mitgeliefert.
<?php
declare(strict_types=1);
namespace CodingMs\Glossaries\XmlSitemap;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Seo\XmlSitemap\RecordsXmlSitemapDataProvider as ParentProvider;
final class RecordsXmlSitemapDataProvider extends ParentProvider
{
/** @var array<int,int> Cache: glossaryUid => firstCategoryUid */
private array $firstCategoryUidCache = [];
protected function defineUrl(array $data): array
{
$record = $data['data'] ?? null;
if (!is_array($record) || empty($record['uid'])) {
return parent::defineUrl($data);
}
$glossaryUid = (int)$record['uid'];
// Only fix if the DB field is not already a usable UID
// (with MM "count" behaviour this is usually 1 for all records, which is wrong)
$firstCategoryUid = $this->resolveFirstCategoryUid($glossaryUid);
if ($firstCategoryUid > 0) {
// Make fieldToParameterMap pick the correct category UID
$record['categories'] = $firstCategoryUid;
$data['data'] = $record;
}
return parent::defineUrl($data);
}
private function resolveFirstCategoryUid(int $glossaryUid): int
{
if (isset($this->firstCategoryUidCache[$glossaryUid])) {
return $this->firstCategoryUidCache[$glossaryUid];
}
$queryBuilder = $this->getQueryBuilder('tx_glossaries_glossary_glossarycategory_mm');
$row = $queryBuilder
->select('uid_foreign')
->from('tx_glossaries_glossary_glossarycategory_mm')
->where(
$queryBuilder->expr()->eq(
'uid_local',
$queryBuilder->createNamedParameter($glossaryUid, ParameterType::INTEGER)
)
)
->orderBy('sorting_foreign', 'ASC')
->addOrderBy('uid_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
$firstUid = (int)($row['uid_foreign'] ?? 0);
return $this->firstCategoryUidCache[$glossaryUid] = $firstUid;
}
private function getQueryBuilder(string $table): QueryBuilder
{
return GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
}
}
TypoScript: SEO-Sitemap auf deinen Provider umstellen
Du ersetzt nur den Provider auf CodingMs\Glossaries\XmlSitemap\RecordsXmlSitemapDataProvider – der Rest kann bleiben.
Configuration/TypoScript/setup.typoscript
plugin.tx_seo.config.xmlSitemap.sitemaps.glossary glossaries {
provider = CodingMs\Glossaries\XmlSitemap\RecordsXmlSitemapDataProvider
config {
table = tx_glossaries_domain_model_glossary
sortField = title
lastModifiedField = tstamp
recursive = 1
# Speicherort der Records
pid = 1200
url {
# PageID der Detailseite
pageId = 2038
fieldToParameterMap {
uid = tx_glossaries_glossary[glossary]
categories = tx_glossaries_glossary[selectedCategory]
}
additionalGetParameters {
tx_glossaries_glossary.controller = Glossary
tx_glossaries_glossary.action = show
}
}
}
}
Hinweis:
Die
additionalGetParametersmüssen zu deinem Plugin-Namespace passen (tx_<extkey>_<plugin>). Wenn dein Plugin anders heißt, musst du das anpassen.
