TYPO3 ermöglicht es, eigene Seitentypen zu erstellen und unseren Redakteuren zur Verfügung zu stellen. In diesem Artikel stelle ich vor, wofür man das verwenden kann, wann es praktisch ist und wann eher nicht und wie es funktioniert.

Wofür braucht man eigene Seitentypen?

In TYPO3 sind Websites (oder was immer man eigentlich mit Inhalt befüllen möchte) immer in Seiten struktiert. Das macht es auch ungeübten Redakteuren einfach, sich auf der Website zurecht zu finden - auch wenn sie größer wird. Diese Seiten werden im Backend im Seitenbaum dargestellt und bilden in der Regel die Struktur im Frontend 1:1 ab. Der Redakteur kann damit sehr einfach an der korrekten Stelle eine neue Seite einfügen oder Inhaltselemente auf einer Seite ändern. 

TYPO3 bringt von Haus aus einige Seitentypen mit. Mit Seiten vom Typ "Standard" werden die normalen Seiten einer Internetpräsenz aufgebaut. Der Seitentyp "externe URL" kann z.B. verwendet werden, um Links zu externen Websites in der Hauptnavigation darzustellen. Und "Systemordner" werden im Menu nicht dargestellt, eignen sich aber sehr gut dazu, um z.B. Footer-Menus zu strukturieren oder als Datenspeicher für Nachrichten oder andere Datensätze bereitzustellen. 

Wofür braucht man nun also noch mehr Seitentypen?

Ich bin ein Freund davon, möglichst wenig Abhängigkeiten in sein System zu bringen. Eine großartige Extension, die in vielen TYPO3-Systemen im Einsatz ist (und definitiv ihre Daseinsberechtigung im TYPO3-Universum hat) ist news (hier im TER) von Georg Ringer. Leider wird diese Extension oft dafür verwendet, wirklich alles in einen News-Datensatz zu quetschen, auch Dinge die da wirklich nicht hineingehören. Oftmals wird dies von unerfahrenen Integratoren gemacht, die auf diese Weise Übersichtsseiten zusammenstellen wollen. Bitte vergesst das sofort wieder: ihr tut damit Redakteuren keinen Gefallen. 

Ich habe unlängst für einen großen Kunden am Relaunch einer Website des Konzerns gearbeitet. Auf dieser Website gibt es verschiedene Inhalte, z.B. "Artikel" die wiederum an anderen Stellen angeteasert werden sollen. Eine Aufgabe für news? Vielleicht. Ganz sicher hätte man dies mit news lösen können; aber wir sind einen anderen Weg gegangen und die Redakteure waren begeistert. 

Übersicht im Backend

Die verschiedenen Beiträge hatten unterschiedliche Anforderungen. Bei einigen Typen waren GPS-Koordinaten notwendig, andere haben Daten für die Zusammenarbeit einer externen API angefragt und wiederum andere konnten z.B. farblich gekennzeichnet werden. Wir haben uns entschieden, all dies mit einzelnen Seitentypen zu realisieren. Für den Redakteur ist das sehr übersichtlich: er sieht direkt im Seitenbaum, um was für einen Typ Seite es sich handelt. Eine neue Seite kann wie gewohnt einfach in den Seitenbaum eingefügt werden. Und jeder Seitentyp fragt genau die Informationen ab, die benötigt werden - und nichts mehr: Alle anderen Felder sind ausgeblendet.

Datenaggregation leicht gemacht

Bei den gewünschten Inhaltselementen, die einen oder mehrere Teaser anzeigen, kann der Kunde nun frei entscheiden, welche Seitentypen berücksichtigt werden sollen und welche nicht. Da alle Seitentypen echte TYPO3-Seiten sind, kann der Redakteur diese ganz einfach im Hauptmenu verwenden, ganz ohne Verrenkungen und ohne einen Entwickler fragen zu müssen. 

Einfache Erweiterbarkeit

Jeder Entwickler kennt das: Kaum ist die Spezifikation aufgeschrieben, ändern sich die Anforderungen. In unserem konkreten Beispiel kam der Wunsch auf, auch Links auf externe Seiten in die Teaser einstreuen zu können. Diese Anforderung war sehr leicht umzusetzen, weil "Link zu externer URL" ein bereits von TYPO3 mitgelieferter Seitentyp ist, und wir daher nur die benötigten Felder (z.B. ein Vorschaubild für den Teaser) als Pflichtfeld für diesen Seitentyp definieren und in der Abfrage einen zusätzlichen Seitentyp konfigurieren mussten. 

Da alle Seitentypen in derselben TYPO3-Tabelle pages gespeichert werden, können wir darüber alle Datenbankmagie anwenden, die uns so einfällt. Wir wollen alle Artikel und externe URLs einer bestimmten Kategorie, nach Datum aufsteigend sortiert? Kein Hexenwerk - so sei es! 

 

Man braucht also Extensions wie news gar nicht mehr?

Soweit würde ich nicht gehen. News ist ein sehr leistungsfähiges Tool (und davon abgesehen kann man einiges lernen, wenn man sich den Quelltext der Extension genauer ansieht). Mein Problem mit news ist eher, dass es zu leistungsfähig ist und man in der Regel nur einen Bruchteil der Funktionalität benötigt. 

Wann news oder eine vergleichbare Extension eine gute Idee ist und wann man lieber eine Lösung baut, wie wir es getan haben, ist immer eine Individualentscheidung. Zudem muss man sich vor der Entscheidung einige Fragen stellen. Ich würde folgende Faustformel formulieren: 

Sollen eine große Anzahl von Datensätzen strukturiert angezeigt werden, wobei die Datensätze immer gleich aussehen, spricht das für eine Extension wie news.
Sind die Datensätze nicht gleichförmig, sondern sehr individuell - in der Darstellung im Frontend und/oder der zu erfassenden Daten im Backend, würde ich eher den hier beschriebenen Weg gehen.

Es gibt hier kein absolut richtig oder falsch; aus meiner Sicht sollten wir Entwickler als oberste Direktive die einfache Handhabung durch die Redakteure im Blick haben. Hier hilft es mir oft, mich explizit mit einem Nicht-Entwickler aus unserem Team zu besprechen, um die Entwicklerbrille abzulegen und die beste Lösung für die Redakteure zu finden. 

Lasst uns einen Seitentypen bauen

Nachdem unsere Entscheidung also getroffen wurde und wir neue Seitentypen verwenden wollen, ist es nun an der Zeit, einen solchen zu entwickeln. Ich gehe davon aus, dass wir nach Best-Practice eine Extension haben, die unser Template beinhaltet und die wir für unsere Zwecke erweitern können. Ich nenne diese Extension sitepackage, aber natürlich ist jede Extension dafür geeignet.

Alle Informationen, die TYPO3 zu Seiten speichert, landen in der Datenbank in der Tabelle pages. Und genau diese Konfiguration passen wir in typo3conf/ext/sitepackage/Configuration/TCA/Overrides/pages.php an: 

 

<?php
defined('TYPO3_MODE') || die();

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
    'pages',
    'doktype',
    [
        'LLL:EXT:sitepackage/Resources/Private/Language/locallang_be.xlf:pagetype.article',
        90,
        'actions-newspaper'
    ],
    '6',
    'after'
);


$fields = [
    'reading_time' => [
        'exclude' => true,
        'label' => 'LLL:EXT:sitepackage/Resources/Private/Language/locallang_be.xlf:pages.reading_time',
        'config' => [
            'type' => 'input',
            'size' => 30,
            'eval' => 'int,trim'
        ],
    ],
];

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns(
    'pages',
    $fields
);

\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule(
    $GLOBALS['TCA']['pages'],
    [
        'ctrl' => [
            'typeicon_classes' => [
                90 => 'actions-newspaper',
            ],
        ],
        'types' => [
           
            // Article
            90 => [
                'showitem' => '
                    --div--;LLL:EXT:sitepackage/Resources/Private/Language/locallang_be.xlf:pages.tabs.article,
                        --palette--;;article,
                        --palette--;;headlines,
                        --palette--;;teaser_image,
                    --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.tabs.access,
                        slug, --linebreak--,
                        starttime, endtime,--linebreak--,
                        --palette--;;visibility,
                        no_search,
                    --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
                    '
            ],

 

hier am Beispiel des doktypes 90 (von mir gewählt) für den neuen Seitentyp "Artikel". Dieser erhält ein eigenes Icon, ggf. werden ein oder mehrere neue Felder (hier: reading_time) definiert und anschließend abhängig vom Seitentyp zugeordnet. Damit sehen die Redakteure nun nur noch die Felder, die für diesen Seitentyp relevant sind. 

Wenn wir neue Felder einfügen, müssen wir diese natürlich auch in der Datenbank definieren. Das machen wir in der ext_tables.sql

 

CREATE TABLE pages (
    reading_time int DEFAULT '0' NOT NULL  
);

 

Bei der Installation der Extension wird die Tabelle entsprechend erweitert: während der Entwicklung können wir das z.B. mit

 

vendor/bin/typo3cms database:updateschema

 

forcieren. 

Damit die Redakteure den neuen Seitentyp wie alle anderen auch per Drag'n'Drop verwenden können, fehlt noch eine TSconfig-Einstellung:

 

options {
  pageTree {
    doktypesToShowInNewPageDragArea := addToList(90)
  }
}

 

Nun können die Redakteure Seiten des Typs Artikel an die gewünschte Stelle ziehen und die Seiteneigenschaften zeigen nur noch die Felder, die wir benötigen. Soweit so gut. :-) 

Feldkonfiguration abhängig vom Seitentyp

Wenn man nun einige Seitentypen ins System einbaut, kann es durchaus vorkommen, dass ein Feld in mehreren Seitentypen vorkommt (kein Problem!) und in dem einen Seitentyp ein Pflichtfeld ist, in einem anderen aber nicht. Was nun? 

Auch dieser Fall kann mit dem TCA gelöst werden. Wir gehen dazu wieder in die oben erstellte typo3conf/ext/sitepackage/Configuration/TCA/Overrides/pages.php und machen das Feld reading_time einmal mandatory und einmal optional: 

 

\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule(
    $GLOBALS['TCA']['pages'],
    [
        'ctrl' => [
            'typeicon_classes' => [
                90 => 'actions-newspaper',
                100 => 'actions-globe',
            ],
        ],
        'types' => [
            // Standardseite
            1 => [
                'columnsOverrides' => [
                    'reading_time' => [
                        'config' => [
                            'eval' => 'required,int,trim'
                        ],
                    ],
                ],
                'showitem' => '...'
            ],
            [...]
        ],
    ],
);

 

Das Feld haben wir vorhin als optional definiert; auf der Standardseite wird es aber nun required. Hier haben wir nun nur einen einzelnen Wert überschrieben; wir können aber bei Bedarf auch das komplette Feld überschreiben und so z.B. reading_time auf der Standardseite als Input-Feld und auf einer Artikelseite als Select-Box mit vordefinierten Werten im Backend anzeigen.

Darstellung im Frontend anpassen

Vielleicht möchten wir einzelne Seitentypen im Frontend anders darstellen. In unserem Anwendungsfall wurden neben der Lesedauer noch zahlreiche andere Felder eingefügt, so können Redakteure z.B. einzelne Seitentypen farblich kennzeichnen und vieles mehr. Mit den im Backend durch die Redakteure abgespeicherten Werte wollen wir natürlich im Frontend arbeiten. Hierfür können wir z.B. ein anderes Template für das Rendering ansteuern: 

 

page {
  10 = FLUIDTEMPLATE
  10 {
    layoutRootPaths {
      10 = EXT:sitepackage/Resources/Private/Layouts/
    }
    templateRootPaths {
      10 = EXT:sitepackage/Resources/Private/Templates/
    }
    partialRootPaths {
      10 = EXT:sitepackage/Resources/Private/Partials/
    }

    file.stdWrap.cObject = CASE
    file.stdWrap.cObject {
      key.data = levelfield:-1,doktype
      1 = TEXT
      1.value = EXT:sitepackage/Resources/Private/Templates/Default.html
      90 = TEXT
      90.value = EXT:sitepackage/Resources/Private/Templates/Article.html
    )
}

 

Falls nötig, können wir die Daten noch mit DataProcessors anreichern, z.B. wenn wir Informationen aus anderen Datenbanktabellen benötigen, die mit unserem Datensatz verknüpft sind: 

 

page.10 {
  dataProcessing {
    900 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor
    900 {
      if {
        isEqual {
          field = doktype
          value = 90
        }
        isTrue {
          field = related_tags
        }
      }
      selectFields = pages.uid, pages.byline, pages.title
      table = pages
      join = tx_sitepackage_domain_model_tag_mm as mm ON mm.uid_local=pages.uid JOIN tx_sitepackage_domain_model_tag as tag ON mm.uid_foreign=tag.uid
      where {
        field = related_tags
        dataWrap = doktype=90 AND pages.deleted=0 AND pages.hidden=0 AND tag.uid IN (|)
      }
      groupBy = pages.uid
      orderBy = pages.crdate DESC
      max = 5
      pidInList = 1
      recursive = 99
      as = relatedArticles
      dataProcessing {
        10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
        10 {
          references.fieldName = media
          as = media
        }
      }
    }
  }
}

 

Hier im Beispiel holen wir uns verwandte Artikel aus der Datenbank und übergeben diese dem View, der diese dann weiterverwenden kann: 

 

<f:if condition="{relatedArticles}">
  <f:for each="{relatedArticles}" as="relatedArticle">
    <div class="col-12">
     <f:link.page pageUid="{relatedArticle.data.uid}">
       <picture class="teaser__image">
          <f:for each="{relatedArticle.media}" as="image">
            <f:image image="{image}" />
		  </f:for>
	   </picture>
       <p>{relatedArticle.data.title}</p>
	 </f:link.page>
    </div>
  </f:for>
</f:if>

 

 

Eine Übersichtsseite erstellen

Zu unserem Glück fehlt uns nun noch die Übersichtsseite über die Seiten. Wie das geht habe ich gerade im vorherigen Absatz schon verraten: die relatedArticles sind ja nichts anderes als Teaser auf andere Seiten, die wir mit allen Eigenschaften, die wir benötigen, über einen TYPO3-DataProcessor einfach bekommen. Vielleicht ist es dem ein oder anderen Leser schon aufgefallen: dieser Artikel arbeitet nur mit TYPO3-Bordmitteln. Wir haben auch kein Repository o.ä. erstellt, diese Lösung kommt ohne Extbase aus und nutzt nur die Standardfunktionen im Core. 

Um eine Übersicht über alle unsere Seiten (eines Typs, oder gemischt, oder was der Kunde sich halt so wünscht) zu generieren, erstellen wir ein Content-Element, das uns die gewünschte Übersicht erstellt und das der Redakteur an allen möglichen Stellen verwenden kann, wo er es eben braucht.  

Über einen DataProcessor suchen wir uns alle benötigten Daten aus der Datenbank und rendern diese im View des Inhaltselementes. Für das hier vorliegende Projekt haben wir hierfür einen eigenen DataProcessor erstellt, der einige Magie beherrscht und Businesslogik kapselt. Für den normalen Anwendungsfall kommt man aber auch mit den TYPO3-DataProcessoren sehr weit und kann sehr coole Dinge damit bauen. 

Die DataProcessors kann man verschachteln (s.o.), so dass TYPO3 einem z.B. das korrekte File-Objekt zu jeder Seite liefert, das man dann im Template einfach nutzen kann. 

Kommentare (8)

  • Vaci
    Vaci
    am 04.01.2021
    Hi,

    ich kann hier auch einen Blick auf die Extension Flexible Pages (https://extensions.typo3.org/extension/flexible_pages/) empfehlen. Damit ist es möglich über eine einfache Config eigene Page Types zu erstellen. Die oben beschriebenen Konfigurationen, die für TYPO3 notwendig sind, werden hier automatisch durchgeführt.

    Wie man z.B. einen Page Type für News erstellt zeigt die Extension Flexible News (https://extensions.typo3.org/extension/flexible_news/).

    Viele Grüße
    Vaci
    • Lennart
      Lennart
      am 05.01.2021
      Moin Vaci, die Extension sieht interessant aus. Braucht man nach Erstellung der Pagetypes noch die Extension oder ist die danach überflüssig?
    • Vaci
      Vaci
      am 07.01.2021
      Tach Lennart,

      die Extension muss installiert bleiben. Die TYPO3 eigene Konfiguration wird durch eine YAML Config "on the fly" (jedoch gecached) generiert. Die Extension ist sehr schlank und wir haben versucht so nah am Core zu arbeiten, wie möglich.

      Viele Grüße
      Vaci
    • Lennart Theede
      Lennart Theede
      am 07.01.2021
      Moin Vaci,

      ich konnte die Extension leider nicht via Composer installieren. Den Fehler habe ich bei GitHub hinterlegt:

      https://github.com/itplusx/flexible_pages/issues/7

      Herzliche Grüße
      Lennart
    • Ramón
      Ramón
      am 14.01.2021
      Hi Lennart,
      wir haben soeben die v2.1.2 released. Der Fehler sollte nun behoben sein. :) Danke für den Hinweis.

      Viele Grüße,
      Ramón
  • Andreas K.
    Andreas K.
    am 25.01.2021
    Hi Marc,
    vielen Dank für Deine tolle Anleitung. Für manche ist evtl. der Hinweis hilfreich, dass es sich bei der TSconfig-Einstellung
    options.pageTree.doktypesToShowInNewPageDragArea
    um User-TSconfig andelt.
    Viele Grüße,
    Andreas
  • David
    David
    am 22.09.2022
    Hallo,

    danke für die Anleitung.
    Alles nachgebaut und es läuft soweit - nur:
    das Erstellen einer neuen Seite via Drag&Drop über den Seitenbaum funktioniert nicht, weil scheinbar nicht erlaubt? Gibts noch eine andere Stelle, neben der UserTS, an der das explizit erlaubt werden muss?

    Grüße
    D

Neue Antwort auf Kommentar schreiben