Wie angekündigt, kommt hier der leidvollen Erfahrungen mit (in Version 4.5) Teil 2. Diesmal mit Hacks im Code der Plugins rund um RSS und HTML-Metadaten. Fangen wir mit einem RSS-Reader an.

RSS-Reader mit konfigurierbarer Ausgabe (Einbindung von RSS-Feeds in eigene Seite).

Einen recht gelungenen (on-demand)-RSS-Reader für Typo3 stellt die Erweiterung tw_rssfeeds dar. Wer das Datum in der Auflistung vermisst, patcht einfach typo3conf/ext/tw_rssfeeds/pi1/class.tx_twrssfeeds_pi1.php in Zeile 280 und macht daraus 2 Zeilen.
$content .= '<div class="twrss_bodytext twrss_item_link"><span class="twrss_item_date">'.date('d. m. Y:',strtotime($this->data['ITEM'][$i]['PUBDATE'])).'</span> ';
$content .= '<a href="'.$this->data['ITEM'][$i]['LINK'].'" target="'.$get_theLinkTarget.'" >'.$go_the_head.'</a></div>';

Der Abstand zwischen Tag und Monat sowie Monat und Jahr kann man mit einem negativen „word-spacing: -0.2ex;“ auf die Klasse „twrss_item_date“ fixieren. Ohne Leerzeichen sieht es bei uns etwas gepresst aus…

RSS generieren mit ecorss

Nachdem tt_news mit der Version 4.5 nicht mehr nutzbar ist, muss man alternativen überlegen. Nach längerer Suche fand ich etwas schicken und einfaches – mit einigen Macken. So erlaubt die Konfiguration die Übergabe von „page:uid“ an pidRootline (Elternelement für einen Auszug aus Änderungen dieser und ihrer Unterseiten). Schrecklich! Lässt man pidRootline leer, werden alle Seiten (egal welcher Domäne) ausgegeben – das kann ja auch nicht der Sinn der Sache sein. Setzt man keine Ganzzahl (Seiten-ID) ein, bekommt man gar nichts. So heißt es: Selbst ist der Webmaster!
if($pidRootline == 'page:uid')
{
$pidRootline = intval($GLOBALS['TSFE']->id);
}

in Funktion load() – gegenwärtig Zeile 60 der Datei class.tx_ecorss_models_feed.php im Ordner models – einfügen behebt diesen Mangel. Man kann auf diesem Wege zumindest page:uid nutzen und mit einem Template für jede Seite (und Unterseiten) ein RSS erzeugen lassen.

RSS-Link im HTML-head einfügen

Das Einfügen von link-Elementen zu RSS/Atom-Feeds der aktuellen Seite kann zwar automatisiert werden, doch wirkt die URL etwas antiquarisch. Mann kann trotz RealURL auf Übergabeparameter nicht verzichten. Es ist unschön, funktioniert aber.
### ATOM-Feed als PAGE des Typs 105 definieren
atom_thispage = PAGE
atom_thispage {
typeNum = 105
config {
disableAllHeaderCode = 1
disableCharsetHeader = 1
additionalHeaders = Content-type:text/xml
no_cache = 1
xhtml_cleaning = 0
admPanel = 0
}
10 >
10 = USER
10.userFunc = tx_ecorss_controllers_feed->display
10{
cache_period = 3600
numberItems = 10
feed = atom
pidRootline = page:uid
select {
01 {
table = tt_content
title = header
summary = bodytext
published = tstamp
updated = tstamp
debug = 0
}
}
}
### Ähnlich RSS-Feed, nur eben als Typ = 106
rss_thispage < atom_thispage
rss_thispage.typeNum = 106
rss_thispage.10.feed = rss
### Übersetzungen für Titel und Untertitel......
page.headerData.12 = USER
page.headerData.12.userFunc = tx_ecorss_controllers_feed->add
page.headerData.12.atom {
typeNum = 105
feed = atom
# aus der konfigurierten Seite des Typs 105 den entsprechend übersetzten Titel übernehmen
title < atom_thispage.10.title
# Und bitte für jede Seite (samt ihren Unterseiten) anbieten! -> Code-Hack unten
pidRootline = page:uid
}
page.headerData.12.rss {
typeNum = 106
feed = rss
# aus der konfigurierten Seite des Typs 105 den entsprechend übersetzten Titel übernehmen
title < rss_thispage.10.title
# Und bitte für jede Seite (samt ihren Unterseiten) anbieten! -> Code-Hack unten
pidRootline = page:uid
}

Die Übersetzungen geht man im Übrigen immer so an: Den Namen der Get/Post-Variablen der Sprache (bei uns „L“) auf Übereinstimmung mit der ID einer Sprache prüfen, dann die Variablen neu belegen.
[globalVar = GP:L = 2] # Die 2 entspricht bei uns en-GB, kann bei Ihnen aber anders sein...
#englitsch
atom_thispage.10 {
title = My page title
subtitle = Updates on this page
lang = en-GB
sysLanguageUid = 2
}
rss_thispage.10 {
title = My page title
subtitle = Updates on this page
lang = en-GB
sysLanguageUid = 2
}
[global]

Wer statt „?type=105“ (o. Ä.) einen Pseudo-Dateinamen haben will, muss zwei Sachen angehen:

Code anpassen

Das Problem ist das Plugin selbst: es ist auf die Anzeige ganzer Zweige ausgelegt, mißachtet aber page:uid. Das müssen wir angehen!
class.tx_ecorss_controllers_feed.php im Verzeichnis controllers des Plugins diese Zeilen in der Funktion add ein:
if($config['pidRootline'] == 'page:uid')
{
$rootPid = intval($GLOBALS['TSFE']->id);
}
elseif($config['pidRootline']+0 == $config['pidRootline'])
{
$rootPid = intval($config['pidRootline']);
}
if( !empty($rootPid) )
{
$feedURL = $this->cObj->getTypoLink_URL($rootPid, array("type" => $config['typeNum']));
}

(bei mir so um die Zeile 82) …da das Original einige Zeilen höher immer(!) die Hauptseite (leveluid:0) annimmt. Da wir auf ein globales RSS für die ganze Domäne (leveluid:0) verzichten wollen und page:uid immer vorgeben werden („diese Seite mit ihren Unterseiten“), müssen wir page:uid auch bedienen – so überschreiben wir $rootPid, die auf den leveluid:0 zeigt. Danach gibt es die drei Möglichkeiten

  • Das RSS bekommt gar keine $rootPid (pidRootline in Konfig) vorgegeben -> die gesamte Domäne wird berücksichtigt (leveluid:0 = Standardverhalten)
  • Eine pidRootline wurde als Zahl angegeben (ein bestimmter Zweig) – dieser Zweig wird mit allen Unterseiten im RSS angezeigt (fest dieser ID zugeordnet)
  • page:uid wurde angegeben -> Immer die aktuelle und die Unterseiten anzeigen

RealURL zur Zusammenarbeit bewegen und schöne URL für Atom und RSS erzeugen

Entgegen der üblichen Überzeugung, dass man „fileName“-Array benötigt, werden unsere Definitionen im „postVarSets“ stehen. Hier kann man definieren, dass ein bestimmter PAGE-Typ einem Pseudo-Dateinamen zugeordnet sein soll.
"postVarSets" => array(
"_DEFAULT" => array (
"atom.xml" => array(
'type' => 'single',
'keyValues' => array ("type" => 106)
),
"rss.xml" => array(
'type' => 'single',
'keyValues' => array ("type" => 105)
)
)
),

Das Problem mit dem Cache in ecorss

Jetzt dürfte alles so weit gehen: link-Elemente im HTML-Kopfbereich, die RSS/Atom-Ausgabe selbst… Doch da ist was faul. Man bekommt Inhalte anderer Seiten angezeigt. Die Funktion defaultAction hat eine kleine Macke: Sie erstellt den Dateinamen für die gecachte RSS-cache-Datei aus unserer Konfiguration (die für alle Seiten gleich ist) und aus dem Seitentyp (der ja auch für RSS bzw. Atom immer gleich bleibt). Fügt man die ID der aktuellen Seite hinzu, wird es eindeutig:

Aus:
$hash = md5(serialize($this->configurations) . $GLOBALS['TSFE']->type);
mach
$hash = md5(serialize($this->configurations) . $GLOBALS['TSFE']->type);
if($this->configurations->_iterator->array['pidRootline'] == 'page:uid')
{
$hash = md5(serialize($this->configurations) . $GLOBALS['TSFE']->type . $GLOBALS['TSFE']->id);
}

Die wunderbare Welt der Head-Elemente

Sollte der Leser den Autor bis jetzt nicht gekannt haben, wäre es an der Zeit, ihm ein kleines offenes Geheimnis zu verraten: Der Autor ist zwar kein Bibliothekar, dennoch lebt er in einer Welt voller Metadaten. Die Metadaten kann man für Suchmaschinenoptimierung verwenden – wenn auch gegenwärtig in einem nur sehr beschränkten Maße. Die Welt besteht nicht nur aus Suchmaschinen. Harvester – wie der der Deutschen Nationalbibliothek – grasen nicht nur für & Co. Auch Archive haben ein berechtigtes Interesse daran, die vom Menschen vergebenen Beschreibungen für einen künftigen Benutzer an stelle des Inhalts anzuzeigen und somit eine grobe Vorauswahl aus einer Treffermenge zu ermöglichen. Dies geschieht in den meta-Elementen des HTML-head-Elementes. Damit es aber lustiger wird, gibt es davon zwei Arten: die alten Meta-Tags und die DublinCore-Metatags.

In der erweiterten Optionspalette der Seiten findet der Typo3-Redakteur Felder wie: Autor, Beschreibung, Schlagworte, Zusammenfassung. Diese Eingabefelder nutzt ein Typo-Skript von typo3blogger.de, um die Daten in beiden Formaten auszugeben. Da wir das Feld „Autor“ so gut wie nie nutzen und keine spezielle Lizenz einsetzen wollen, lassen wir diese zwei Sachen aus. (Von uns ungenutzte Felder wurden auskommentiert.) Fehlen Daten zum Einfügen, werden dank required = 1 bzw. Vererbung keine (leeren) Tags generiert.
lib.title = TEXT
lib.title.data = field:subtitle // leveltitle :-1,slide
lib.description = TEXT
lib.description.data = levelfield :-1, description, slide // levelfield :-1, subtitle, slide // leveltitle :-1,slide
lib.keywords = TEXT
lib.keywords.data = levelfield :-1, keywords, slide // levelfield :-1, subtitle, slide // leveltitle :-1,slide
#lib.author = TEXT
#lib.author.data = levelfield :-1, author, slide
#lib.copyright = TEXT
#lib.copyright.data = levelfield: 0, author

##### Zeilenumbruch (ist verzichtbar) ######
lib.emptyLine = TEXT
lib.emptyLine.value (

)

############## Meta ###################
lib.meta.description =< lib.description
lib.meta.description {
required = 1
outerWrap = <meta name="description" content="|"/>
}
lib.meta.keywords =< lib.keywords
lib.meta.keywords {
required = 1
outerWrap = <meta name="keywords" content="|"/>
}
#lib.meta.author =< lib.author
#lib.meta.author {
# required = 1
# outerWrap = <meta name="author" content="|"/>
#}
#lib.meat.copyright =< lib.copyright
#lib.meta.copyright {
# required = 1
# outerWrap = <meta name="copyright" content="|"/>
#}

############# DublinCore-Meta ################
lib.meta.dc.title =< lib.title
lib.meta.dc.title {
required = 1
outerWrap = <meta name="DC.Title" content="|"/>
}
lib.meta.dc.description =< lib.description
lib.meta.dc.description {
required = 1
outerWrap = <meta name="DC.Description" content="|"/>
}
lib.meta.dc.subject =< lib.keywords
lib.meta.dc.subject {
required = 1
outerWrap = <meta name="DC.Subject" content="|"/>
}
#lib.meta.dc.creator =< lib.author
#lib.meta.dc.creator {
# required = 1
# outerWrap = <meta name="DC.Creator" content="|"/>
#}
#lib.meat.dc.right =< lib.copyright
#lib.meta.dc.rights {
# required = 1
# outerWrap = <meta name="DC.Rights" content="|"/>
#}

###########################
# Header zusammenbauen ####
###########################
page.headerData.999 = COA
page.headerData.999 {
5 =< lib.emptyLine
10 =< lib.meta.title
15 =< lib.emptyLine
20 =< lib.meta.description
25 =< lib.emptyLine
30 =< lib.meta.keywords
35 =< lib.emptyLine
#40 =< lib.meta.author
#45 =< lib.emptyLine
#50 =< lib.meta.copyright
#55 =< lib.emptyLine
#60 =< lib.meta.robots
#65 =< lib.emptyLine
70 =< lib.meta.dc.title
75 =< lib.emptyLine
80 =< lib.meta.dc.description
85 =< lib.emptyLine
90 =< lib.meta.dc.subject
95 =< lib.emptyLine
#100 =< lib.meta.dc.creator
#105 =< lib.emptyLine
#110 =< lib.meta.dc.rights
#115 =< lib.emptyLine
}

#### Vorausgesetzt, diese Zeile steht in der localconf.php!
$GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'] .= ‘,subtitle,author,keywords,description’;

Robots-Anweisungen kann man entweder wie oben für jede Seite einzeln aus dem Reiter „Erweitert“ auslesen lassen oder die Datenbank in Ruhe lassen und per Hand global definieren:
page.meta.robots = INDEX,FOLLOW
In einigen Fällen will es mit dem CSS-Stylen im IE nicht so richtig gehen. Es braucht einen Kompatibilitäts-Tag:
page.headTag =<head><meta http-equiv="X-UA-Compatible" content='IE=9' />
oder schöner so:
page.headerData.1 = HTML
page.headerData.1.value = <meta http-equiv="X-UA-Compatible" content="IE=9" />

Sitemaps für die Google & Co.

Eine alte Weißheit besagt, dass man sich um seinen Ruf selber kümmern muss – noch bevor es andere tun. Auch im Internet kann es nicht schaden, die eigenen Webseiten den Suchmaschinen vorzuwerfen. Es ist immer besser, es selber steuern zu können, als zu hoffen und zu beten, dass sich Google und Bing erbarmen… Dafür hat man ja die Sitemaps erschaffen. Für Typo3 gibt es sogar ein gutes Extension: mc_googlesitemap. Man erstellt eine neue Seite mit einem Namen wie „sitemap.xml“ und fügt ihr als Inhaltselement ein Sitemap vom Typ „Sitemap for Sites“ (das dritte blaue „G“). Ab sofort hat man eine Sitemap, die sogar für einzelne Seiten steuerbar ist: Wie oft soll der Roboter vorbeikommen? Wie wichtig ist diese Seite? Wann wurde sie zuletzt geändert?

Schade nur, dass das Plugin nicht mit RealURL zurecht kommt. Oder eher: mit dem Slash am Ende der sprechenden URLs. Aber auch hier kann man einen kleinen Hack anwenden. Man ändert im Hauptverzeichnis des Plugins mc_googlesitemap die Datei class.tx_mcgooglesitemap_base.php in der Zeile 63 von

$tmp=explode("/",$GLOBALS['_SERVER']['PHP_SELF']);

nach

$tmp=explode("/",preg_replace('#(/)$#', '', $GLOBALS['_SERVER']['PHP_SELF']));

Ab jetzt macht der Slash hinter sitemap.xml keine Probleme mehr. Der Pfad wird des Anhängsels „sitemap.xml/“ beraubt und richtig ausgegeben. Es funktioniert auch für unterschiedliche Sprachversionen wie z.B. /de/sitemap.xml -> http://…./de/Seite1/ bzw. /en/sitemap.xml -> http://…./en/Site1/.