TYPO3 / Extbase: tt_content Elemente als IRRE (Inline Relational Record Editing)

tt_content als IRRE

Ist das nicht IRRE?

Heute möchte ich endlich mal einen Beitrag schreiben, den ich schon lange schreiben wollte. Und zwar geht es darum in den Datensätzen seiner TYPO3 Extbase / Fluid Extension „tt_content“-Datensätze als Inline Relational Record Editing (IRRE) Elemente einzubinden.

Dieser Artikel wurde am 18.01.2017 grundlegend aktualisiert und sollte für TYPO3 Version 6.2 bis 8.9.99 kompatibel sein.

Wieso sollte man tt_content als IRRE verwenden?
In meiner Extension gab es komplexe Datensätze mit einer Listen- und Detailansicht. In der Detailansicht konnten diese Datensätze sehr komplex sein und es konnte viele Texte, Bilder, Plugins und so weiter in der Detailansicht geben. Da tt_content all diese Dinge bedienen kann, erschien es mir als sinnvoll sie einfach als IRRE in meine Datensätze einzubauen!

Dieses kleine Tutorial wurde unter TYPO3 6.2.25 getestet. Voraussetzung hierbei ist außerdem eine Extbase Extension (bei pi-Base funktioniert es aber wohl auch). Ziel ist es am Ende in Datensätzen seiner Extension alle Formen von tt_content Elementen einzubetten – sogar andere Plugins!

Was brauchen wir also um das zu erreichen? Zunächst einmal braucht die Tabelle unserer Extbase-Extension Datensätze ein Feld vom Typ INT für die Inhalte (in diesem Fall heißt das TCA Feld einfach „inhalt“).
Außerdem brauchen wir ein Feld in der tt_content Tabelle mit der ID unseres Datensatzes (in meinem Fall heißt er „Artikel“ – fragt nicht warum):

CREATE TABLE tx_meineExtension_domain_model_artikel (
	inhalt int(11) unsigned DEFAULT '0',
);

CREATE TABLE tt_content (
	artikel int(11) unsigned DEFAULT '0' NOT NULL
);

Als nächstes brauchen wir folgenden Eintrag am Ende der ext_tables.php unserer Extension unter typo3conf/ext/meineExtension/Configuration/TCA/Overrides/tt_content.php. Damit teilen wir TYPO3 mit, wie mit diesem neuen Feld zu verfahren ist.

<?php
if (!defined ('TYPO3_MODE')) {
    die ('Access denied.');
}

$TCA['tt_content']['columns']['artikel']['config']['type'] = 'passthrough';

So nun ist es aber auch schon fast geschafft. Nun können in den TCA Einstellungen unseres Extbase Datensatzes unter typo3conf/ext/meineExtension/Configuration/TCA/Artikel.php im „columns„-Array folgendes hinzufügen:

'inhalt' => array(
            'exclude' => 0,
            'label' => 'Inhalte',
            'config' => array(
                'type' => 'inline',
                'foreign_table' => 'tt_content',
                'foreign_field' => 'artikel',
                'maxitems'      => 9999,
                'appearance' => array(
                    'collapseAll' => 0,
                    'levelLinksPosition' => 'top',
                    'showSynchronizationLink' => 1,
                    'showPossibleLocalizationRecords' => 1,
                    'showAllLocalizationLink' => 1
                ),
            ),
        ),

Bitte unbedingt auch daran denken, dieses Feld unter „showitem“ und „showRecordFieldList“ des TCA aufzunehmen, damit wir das neue IRRE Feld später auch beim Erstellen / Bearbeiten des Datensatzes sehen können.

Und fertig! Falls ihr bei der Erstellung eurer Extension den extension_builder verwendet habt und dieses Feld im vorhinein nicht einkalkuliert habt, müsst ihr im Model des Datensatzes noch die entsprechenden GETTER / SETTER sowie die Eigenschaft „Inhalt“ an sich noch anlegen.

/******* UPDATE 12. Dezember 2014:
Falls ihr für die Inhaltselemente kein Model im Extension Manager angelegt habt und dieses auf die Tabelle tt_content mappt, dann muss dies von Hand nachgetragen werden. Das Model muss dazu nicht unbedingt über GETTER und SETTER aller Eigenschaften verfügen. Im Prinzip reicht ein Getter für die UID den man bereits von \TYPO3\CMS\Extbase\DomainObject\AbstractEntity erbt.

Das Model unter typo3conf/ext/meineExtension/Classes/Domain/Model/Content.php könnte also folgendermaßen aussehen:

<?php
namespace TYPO3\MeineExtension\Domain\Model;

/**
 *
 *
 * @package meineExtension
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3 or later
 *
 */
class Content extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {

}
?>

Nun ganz wichtig: Damit Extbase weiß, dass er nicht nach einer tx_meineExtension_content Tabelle in der Datenbank suchen soll, muss in der Configuration/TypoScript/setup.txt der Extension das Mapping des Models auf die Tabelle tt_content umgeleitet werden! Ansonsten erhaltet ihr Datenbankfehler ála „unknown field in where clause“.

plugin.tx_meineextension {
	persistence {
		classes {
			TYPO3\MeineExtension\Domain\Model\Content {
				mapping {
					tableName = tt_content
				}
			}
		}
	}
}

Und hier noch Attribut und Getter aus dem beispielhaften Model „Artikel“

/*....*/

/**
     * Inhalte
     * 
     * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\MeineExtension\Domain\Model\Content>
     */
    protected $inhalt;
    
    /**
     * Gets inhhalt
     * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\MeineExtension\Domain\Model\Content> $inhalt
     */
    public function getInhalt() {
        return $this->inhalt;        
    }

/*....*/

Update Ende *******/

Bei der Ausgabe der Datensätze im Fluid könnte euch später bestimmt der dazugehörige tt_content Viewhelper behilflich sein 😉

Eine Quest bleibt jedoch noch offen, die ich bisher nicht lösen konnte:
Und zwar landen die tt_content Elemente nun automatisch auf der Seite, auf der wir die Elemente als IRRE eingebunden haben. Auf Seiten die im Frontend nicht angezeigt werden, ist das nicht so schlimm. Schöner wäre es dennoch, wenn diese Datensätze keine oder eine PID nach Wahl zugeteilt bekommen. Falls jemand dafür eine Idee, rein in die Kommentare damit (vielleicht ein Hook?).

30 Kommentare

  • Sebastian Virus

    Hi,
    eine Lösung dafür das die Content-Elemente einer anderen Seite gespeichert werden ist folgendes Page-TS:
    TCAdefaults.tt_content.pid = *id*

    • Das würde dann aber für alle tt_content Elemente der betroffenen Seiten gelten, was vielleicht nicht immer sehr nützlich ist.

      • Sebastian Virus

        Bei meinem Test, wirkt es zumindest in TYPO3 6.2 nur auf tt_content Elemente die per IRRE erzeugt werden.
        Wenn man natürlich mehrere unterschiedliche Speicherorte wünscht, muss man die in der jeweiligen Seite auch immer in TS schreiben.

  • Wenn Du die Datensätze aber auf einer Seite mit normalen Inhalten erstellst, lassen sich diese nicht mehr unterscheiden.

  • Verwende einfach eine nicht ausgegebene „colPos“ für die entsprechenden Inhalte, siehe EXT:gridelements

  • Gibt es noch eine andere Möglichkeit das Model anzupassen außer ein Model „EigenerInhat“ anzulegen und dieses auf die Tabelle tt_content zu mappen?

  • Danke für den Tipp Thomas, das ist natürlich mal eine Überlegung wert. Ich werde das bei Zeiten mal probieren zu realisieren.

    @Joey: Du musst ja kein Model „eigenerInhalt“ anlegen. Du brauchst im Elternelement lediglich noch mindestens einen Getter um auf die tt_content Elemente zuzugreifen.

  • Hallo,

    funktioniert das mit der 6.2 noch? Ich bin gerade am Verzweifeln. Im Backend ist alles super, anlegen, speichern löschen alles funktioniert. Aber im Frontend kommt bei „inhalt“ nur die Anzahl an Datensätzen an.

  • Ich habe den Artikel entsprechend aktualisiert.

  • Hallo Paul,

    danke für den sehr hilfreichen Artikel!

    Ich habe das Problem mit der PID etwas anders gelöst: Anstatt die IRRE tt_content ELemente von den Datensätzen zu trennen, stelle ich einfach sicher, dass ich sie immer zusammen in einen eigenen Datensatz Ordner auslagern kann. Dazu will ich natürlich nicht die Allgemeine Datensatzsammlung der gesamten Seite umbiegen, sondern ich stelle für jedes Plugin, dass diese Art Datensätze benötigt, einen eigenen Ordner zum Speichern bereit.

    Dazu muss auf Plugin Ebene eine Möglichkeit zur Angabe einer eigenen Storage PID existieren.

    In eigenen Plugins reicht es dazu im FlexForm ein eigenes Feld „storagepid“ anzulegen und dieses im Controller bei der Ausgabe der Datensätze festzulegen:

    protected function initializeAction()
    {
    parent::initializeAction();

    $configuration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);

    $currentPid[‚persistence‘][’storagePid‘] = $this->settings[’storagepid‘];
    $this->configurationManager->setConfiguration(array_merge($configuration, $currentPid));
    }

    Damit kann man dann für jedes Plugin separat alles in einen eigenen Ordner auslagern.

    Ist das Plugin wie in meinem Fall selbst ein erweitertes tt_content Element, muss tt_content dahingehend noch angepasst werden. In der ext_tables.sql:

    CREATE TABLE tt_content (
    artikel int(11) unsigned DEFAULT ‚0‘ NOT NULL,
    storagepid int(11) DEFAULT ‚0‘ NOT NULL
    );

    In der ext_tables.php:

    $aSubHeaderCols = array (
    ’storagepid‘ => array (
    ‚exclude‘ => 0,
    ‚label‘ => ‚Plugin Datensatzsammlung‘,
    ‚config‘ => array (
    ‚type‘ => ‚group‘,
    ‚internal_type‘ => ‚db‘,
    ‚allowed‘ => ‚pages‘,
    ’size‘ => ‚1‘,
    ‚maxitems‘ => ‚1‘,
    ‚minitems‘ => ‚0‘,
    ’show_thumbs‘ => ‚1‘,
    ‚wizards‘ => array (
    ’suggest‘ => array (
    ‚type‘ => ’suggest‘
    ),
    ),
    ),
    ),
    );

    \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns(
    ‚tt_content‘,
    $aSubHeaderCols
    );

    \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette(
    ‚tt_content‘,
    ‚header‘,
    ‚–linebreak–,storagepid‘,
    ‚after:header_link‘
    );

    Und schließlich im Controller:

    protected function initializeAction()
    {
    parent::initializeAction();

    $configuration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
    $currentTtContent = $this->configurationManager->getContentObject()->data;

    $currentPid[‚persistence‘][’storagePid‘] = $currentTtContent[’storagepid‘];
    $this->configurationManager->setConfiguration(array_merge($configuration, $currentPid));
    }

    (Das angegebene TCA zur Erweiterung von tt_content kann analog in einem eigenen Plugins als FlexForm umgesetzt werden, ebenso das Feld „storagepid“ in der ext_tables.sql).

    Für mich funktioniert das ganz gut, vor allem weil man die eigene Storage PID auch anderweitig nutzen kann.

    Schöne Grüße,
    (auch) Paul

  • Hey Paul!

    Vielen Dank für diese sehr interessante Herangehensweise. Macht auf mich auf jeden Fall einen empfehlenswerten Eindruck! Bei Deinem Eintrag bedaure ich, dass wir noch kein Code-Highlighting in den Kommentaren hier haben, welches wir so schnell wie möglich nachrüsten werden.

  • Florian Schaeffer

    Hallo Paul,

    ich (bzw. der Extension builder) habe statt der tt_content – Erweiterung den Weg über eine MM-Tabelle gewählt, hier scheint dein Ansatz leider nicht zu funktionen.

    In Fluid bleibt das content-Feld immer NULL, egal wie viele Datensätze ich im Backend anhänge (das klappt problemlos).

    Hast du da auch eine Idee?

    Florian

  • Hallo Florian,
    Hat das Model dem Du die Content-Objekte anhängst entsprechende Get und Set Funktion für tt_content Elemente?

  • Ich habe gerade ein Problem bei der Erweiterung von tx_news_domain_model_tag (aus der ext:news) mit einem Feld um tt_content mit IRRE einzubinden:

    Anlegen, Bearbeiten und Löschen funktioniert soweit,
    nur: beim Anlegen wird die UId des Tag-Datensatzes nicht in den tt_content Datensatz eingetragen (alle anderern Felder schon)

    wenn ich dann die UId per Hand (phpmyAdmin/sequel) eintrage kann ich den Datensatz auch bearbeiten und sogar löschen.

    d.h. Löschen = den tt_content Datensatz als deleted markieren (die Referenz zum Tag-Datensatz wird nicht gelöscht) ich weiß nicht ob die Referenz in einem solchen Fall normalerweise erst entfernt wird.

    alles unter 6.2.15

    woran kann es liegen dass die Referenz nicht eingetragen wird? muss für tt_content noch irgendwo etwas speziell konfiguriert werden?

     

  • Kannst Du mal die TCA Konfiguration von den Tags für das tt_content Feld posten?

  • Abdellatif Landolsi

    Hallo,

    Das Content feld bleibt leer  wenn ich eine Repostory abfrage mache, kann mir jemand einen Extension oder einen beispiel empfehelen der eine IRRE mit tt_content  verwendet?

  • Hi Paul,
    danke für Deinen Beitrag, der hat mir sehr weitergeholfen.

    Ich habe den von Dir verlinkten ‚tt_content Viewhelper‘ übernommen und er funktioniert zum Anzeigen eines einzelnen Content Elements wunderbar. Allerdings musste ich bei Typo3 7.6.9 manuell den Inhalt des Ordners /typo3temp/ löschen, vorher hat der ClassAutoloader meinen ViewHelper nicht eingebunden und ich bekam einen entsprechenden Fehler. Nur zur Info falls da noch jemand anderes festbeisst.

    Was ich aber eigentlich will, ist ein Viewhelper der mit alle Content Elemente ausgibt die auf meine Model-Instanz zeigen. Konkret steht ja angelehnt an dein Beispiel in dem Feld ‚artikel‘ der Tabelle tt_content die UID des Model-Eintrags für den es über IRRE angelegt wurde. Anstatt also ein tt_content element über die UID zu selektieren, möchte ich über das Feld ‚artikel‘ selektieren.
    Weisst Du wie das geht?

    • Hallo,

      Dazu bräuchtest Du im TCA von tt_content auf jeden Fall schon mal ein Selectfield für deine „Artikel“. In deinen „Artikeln“ reicht dann vielleicht schon ein Feld „Inhalte“ konfiguriert als passthrough… über diese Konfiguration könnte Extbase vielleicht schon entsprechende Relationen herstellen.

      • Das habe ich leider so nicht hinbekommen, ich habe die entsprechenden Anpassungen an dem TCA gemacht aber die haben nicht dazu geführt dass die Referenz aufgelöst wurde.
        Ich habe das nun für mich anders gelöst: Über die TS-Konfiguration des Plugins habe ich im Bereich „persistence“ ein Klassen-Mapping zu ‚tt_content‘ definiert. Nun werden die tt_content Einträge dereferenziert und ich kann die ‚uid‘ werte über eine Schleife abarbeiten. Zur Darstellung verwende ich im Template dann den tt_content ViewHelper.

  • Hallo,
    bei mir kommt aber immer der Fehler, „unknown Column in where clause“. und zwar kennt mein model den „artikel“ nicht. das klingt irgendwie, als würde das mapping nicht funktionieren? denn „artikel“ ist ja eine Column aus tt_content?

  • Bitte mal einen Lektor drüber lesen lassen. 🙂

    Im 1. Satz direkt 2 Fehler. :/

    • Danke für den Hinweis – hatte den Artikel wohl nach einem langen Arbeitstag geschrieben und werde bei Gelegenheit den Text weiter ausbessern.

  • Hallo Paul,
    danke für Deinen Beitrag. Bezüglich dem Erscheinen des CE’s auf der Seite könnte ein „Dirtytrick“ helfen. Ohne es jetzt detailliert getestet zu haben:

    Einfach ein (externes) BE Layout anlegen, welches z.B. eine Spalte mehr beinhaltet als unter colCount angegeben. Diese Spalte dann im TCA verwenden:

    ‚foreign_record_defaults‘ => array(
    ‚colPos‘ => ‚999‘,
    ‚CType‘ => yourext_pi1′
    ),
    Die Spalte wird im BE nicht angezeigt, dennoch wird der Inhalt gespeichert und ist per Debug sichtbar. Durch die Angaqbe der Spalte erscheint beim CE nicht „invalid column“ sondern der Spaltenname des BE’s.

  • Hallo,
    super Beitrag! Ich hab das alles soweit bereits funktionsfähig, möchte aber die auszuwählenden Inhaltselemnte (CType) einschränken. Das ist wohl über einen „filter“ in der TCA + eigener UserFunction möglich. Hat das schon jemand gemacht? Wenn ja könnte ich dazu mal ein Codebeispiel sehen?
    VG

Schreibe einen Kommentar zu Joey Mani Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Highlighting von Codes ist mit den Tags  [ts], [php], [html], [javascript], [xml] oder [code] möglich.