Contao: Ein eigenes Inhaltselement

von Patrick Froch (Kommentare: 5)

Dieser Text zeigt, wie man in Contao ein eigenes Inhaltselement mit eigenen Feldern erstellt, die Daten verarbeitet und im Frontend ausgibt. Als Beispiel soll ein Inhaltselement dienen, in dem man für ein Produkt eine Überschrift, ein Freitext, eine Liste mit Eigenschaften und einen Preis pflegen kann.

Zunächst legen wir einen Ordner unter TL_ROOT/system/modules/ an. Dieser enthält unsere Erweiterung. Alles was wir tun findet in diesem Ordner statt. Man kann für den Ordner einen beliebigen Namen wählen, in diesem Tutorial heißt er esitcontent. In dem Verzeichnis legen wir als erstes die obligatorischen Ordner einer Contao-Erweiterung an. Es ergbit sich folgende Ordnerstruktur:
esitcontent/
├── classes/
│   └── elements/
├── config/
├── dca/
├── languages/
│   └── de/
└── templates/

Config

Damit wir unser Inhaltselement im Auswahlfeld Elementtyp auswählen können, müssen wir es in der Konfiguration bekannt geben. Hierzu erstellen wir die Datei config/config.php im Verzeichnis unserer Erweiterung, mit folgendem Eintrag:
// config/config.php
$GLOBALS['TL_CTE']['esit']['myproduct'] = '\\esit\\esitcontent\\classes\\elements\\ContentProduct';
Der zweite Key des Arrays (esit) ist die Gruppe, in der der Eintrag angezeigt wird. Der dritte Key gibt den Namen der Palette an (myproduct). Wenn wir nun ein Inhaltselement aufrufen, sollte dort bereits der Eintrag angezeigt werden: Die Einträge werden später natürlich noch übersetzt, so dass dort brauchbare Begriffe stehen.

DCA

Nun erweitern wir das DCA der Tabelle tl_content, da in dieser Tabelle die Daten der Inhaltselemente gespeichert werden. Wir erstellen unsere drei Felder und eine entsprechende Palette:
// dca/tl_content.php
/**
 * Table tl_content
 */
$strName = 'tl_content';


/* Palettes */
$GLOBALS['TL_DCA'][$strName]['palettes']['myproduct'] = '{type_legend},type,headline;{description_legend},productdescription,productproperties,productprice;{invisible_legend:hide},invisible,start,stop;';


/* Fields */
$GLOBALS['TL_DCA'][$strName]['fields']['productdescription'] = array
(
    'label'                   => &$GLOBALS['TL_LANG'][$strName]['productdescription'],
    'exclude'                 => true,
    'inputType'               => 'textarea',
    'eval'                    => array('rte' => 'tinyMCE'),
    'sql'                     => "text NOT NULL"
);

$GLOBALS['TL_DCA'][$strName]['fields']['productproperties'] = array
(
    'label'                   => &$GLOBALS['TL_LANG'][$strName]['productproperties'],
    'exclude'                 => true,
    'inputType'               => 'listWizard',
    'sql'                     => "text NOT NULL"
);

$GLOBALS['TL_DCA'][$strName]['fields']['productprice'] = array
(
    'label'                   => &$GLOBALS['TL_LANG'][$strName]['productprice'],
    'exclude'                 => true,
    'inputType'               => 'text',
    'eval'                    => array('tl_class' => 'w50'),
    'sql'                     => "varchar(255) NOT NULL default ''"
);
Wir verwenden in unserer Palette die erstellten Felden und einige die Contao mitbringt.
Da wir neue Felder erstellt haben, ist es nun an der Zeit, die Datenbank zu aktualisieren.

Languages

Bevor wir uns nun mit der Verarbeitung und Ausgabe der Daten beschäftigen, wollen wir noch die deutschen Begriffe einfügen. Wir legen die Datei languages/de/tl_content.php an.
// languages/de/tl_content.php
/**
 * Table: tl_content
 */
$strName = 'tl_content';

/**
 * Fields
 */
$GLOBALS['TL_LANG'][$strName]['productdescription'] = array('Beschreibung', 'Bitte geben Sie die Beschreibung ein.');
$GLOBALS['TL_LANG'][$strName]['productproperties']  = array('Eigenschaften', 'Bitte geben Sie die Eigenschaften ein.');
$GLOBALS['TL_LANG'][$strName]['productprice']       = array('Preis', 'Bitte geben Sie den Preis ein.');

/**
 * Legends
 */
$GLOBALS['TL_LANG'][$strName]['description_legend'] = 'Eigenschaften';
Damit auch der Eintrag und die Gruppe im Auswahlfeld Elementtyp einen vernünftigen Namen bekommen, legen wir noch die Datei languages/de/default.php an.
// languages/de/default.php
/**
 * Content Elements
 */
$GLOBALS['TL_LANG']['CTE']['esit']      = array('easy Solutions IT');
$GLOBALS['TL_LANG']['CTE']['myproduct'] = array('Produkt');
Wir legen hier einen Eintrag, für die (in der Datei config/config.php angelegte) Gruppe [esit] und einen für die Palette [myproduct] an.

Autoload

Nun greifen wir etwas vor und erstellen einen Eintrag für unsere Ausgabeklasse in der Datei config/autoload.php. Den Namen haben wir bereits in der Datei config/config.php weiter oben festgelegt. Bei dieser Gelegenheit registrieren wir auch gleich das Template, welches wir später anlegen werden.
// config/autoload.php
/**
 * Variables
 */
$strFolder      = 'esitcontent';
$strNamespace   = 'esit\\' . $strFolder;

/**
 * Register the namespaces
 */
ClassLoader::addNamespaces(array
(
	$strNamespace
));

/**
 * Register the classes
 */
ClassLoader::addClasses(array
(
	// Elements
	$strNamespace . '\\classes\\elements\\ContentProduct' => "system/modules/$strFolder/classes/elements/ContentProduct.php"
));

/**
 * Register the templates
 */
TemplateLoader::addFiles(array
(
	'ce_product' => "system/modules/$strFolder/templates",
));
In Zeile 22 registrieren wir unsere Klasse und in Zeile 30 das Template.

Die Ausgabeklasse

Nun erstellen wir die Ausgabeklasse unter classes/elements/ContentProduct.php.
// classes/elements/ContentProduct.php
namespace esit\esitcontent\classes\elements;

/**
 * Class ContentProduct
 * @package esitcontent\classes\elements
 */
class ContentProduct extends \ContentElement
{

    /**
	 * Template
	 * @var string
	 */
	protected $strTemplate = 'ce_product';

	/**
     * Compile the content element
     */
    protected function compile()
    {
        if (TL_MODE == 'BE') {
            $this->genBeOutput();
        } else {
            $this->genFeOutput();
        }
    }

    /**
     * Erzeugt die Ausgebe für das Backend.
     * @return string
     */
    private function genBeOutput()
    {
        $this->strTemplate          = 'be_wildcard';
        $this->Template             = new \BackendTemplate($this->strTemplate);
        $this->Template->title      = $this->headline;
        $this->Template->wildcard   = "### ContentProduct ###";
    }

    /**
     * Erzeugt die Ausgebe für das Frontend.
     * @return string
     */
    private function genFeOutput()
    {
        if ($this->productproperties != '') {
            $this->Template->arrProperties = deserialize($this->productproperties, true);
        }
    }
}
Da die Klasse von \Contao\ContentElement erbt muss sie eine Methode mit dem Namen compile implementieren. Diese ist für die Ausgabe zuständig. Ich splitte an dieser Stelle immer in zwei Methoden auf, eine für die Backend- und eine für die Frontend-Ausgabe. Die Methode genBeOutput ist für die Ausgabe im Backend zuständig. Uns interessiert aber mehr die Methode genFeOutput. Diese erstellt die Ausgabe für das Frontend. Die Eigenschaften werden von Contao als serialisiertes Array gespeichert. In Zeile 48 werden sie deshalb deserialisiert, damit wir sie nutzen können. Wir speichern das Array in $this->Template->arrProperties und haben dann im Template über $this->arrProperties zugriff darauf.

Template

Nun fehlt nur noch die Ausagabe. Wir legen das Template unter templates/ce_product.html5 an.

<div class="<?php echo $this->class; ?> block">

    <!-- Überschrift -->
    <?php if ($this->headline): ?>
        <<?php echo $this->hl; ?>><?php echo $this->headline; ?></<?php echo $this->hl; ?>>
    <?php endif; ?>

    <!-- Beschreibung -->
    <?php if ($this->productdescription): ?>
        <div class="description">
            <?php echo $this->productdescription; ?>
        </div>
    <?php endif; ?>

    <!-- Eigenschaften -->
    <?php if (count($this->arrProperties)): ?>
        <ul>
            <?php foreach($this->arrProperties as $strProperty): ?>
                <li><?php echo $strProperty; ?></li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>

    <!-- Preis -->
    <?php if ($this->productprice): ?>
        <div class="price">
            <?php echo $this->productprice; ?> €
        </div>
    <?php endif; ?>
</div>

Wir können im Template einfach über $this gefolgt vom Feldnamen auf die im Inhaltselement eingegebenen Werte zugreifen (z.B. $this->productdescription für die Produktbeschreibung). Die Eigenschaften haben wir ja in unserer Ausgabeklasse deserialisiert und in der Variable arrProperties gespeichert. Nun gehen wir das Array in Zeile 18 durch und geben die Texte in Zeile 19 aus.

Das war schon der ganze Zauber. Hier zum Abschluss noch eine Übersicht der erstellte Dateien.

esitcontent/
├── classes
│   └── elements
│       └── ContentProduct.php
├── config
│   ├── autoload.php
│   └── config.php
├── dca
│   └── tl_content.php
├── languages
│   └── de
│       ├── default.php
│       └── tl_content.php
└── templates
    └── ce_product.html5

Zurück

Einen Kommentar schreiben

Kommentar von TTC |

Funktioniert leider nicht!

1 zu 1 übernommen, danach bekommt man nur noch einen Serverfehler ausgegeben.

Antwort von Patrick Froch

Dann ist hier vielleicht irgendwo ein Tippfehler. Ohne nähere Informationen ist leider schwer zu sagen, was falsch läuft.

Kommentar von Basnet |

Funktioniert ganz gut! Muss mann ja auf jedenfalls Ahnung von DCA, Palette usw. haben. (Contao 3.5.6)

Kommentar von 404 |

@TTC
ein fehlendes <?php am Anfang jeder Datei (ausser Template) wird vermutlich das Problem gewesen sein.

Antwort von Patrick Froch

Ja, dass kann gut sein. So viel PHP-Kenntnisse habe ich wohl unbewusst vorausgesetzt.

Kommentar von Torsten Tiedt |

Wow. Hat auf Anhieb funktioniert.

Vielen Dank für das Tutorial. Echt nützlich, insbesondere weil die Contao-Doku ja doch recht löchrig ist, wie ich finde.

Gruß!

Kommentar von Fredi Gut |

Super: Eine tolle Anleitung, dei auf Anhieb funktioniert und mit den Kommentaren auch gut nachvollziehbar ist. Ich bin auch der Meinung, dass es besonders hilfreich ist wegen der doch etwas löchrigen Contao-Dokumentation.
Ich wünsche mir weiter soclhe Anleitungen. Danke