[2330 Aufrufe]

2.2 Eigene Daten im Backend verwalten

Nachdem wir im letzten Beitrag eine Hilfeseite für das Backend erstellt haben, wollen wir nun unsere erste eigene Tabelle für die Verwaltung von benutzerdefinierten Daten erstellen.

Später werden wir noch Inhaltselemente und Module implementieren. All dies benötigt aber in der Regel Anpassungen am DCAs (Data Container Array). Es gibt ein DCA pro Tabelle und es ermöglicht uns nicht nur unsere eigenen Tabellen in Contao zu integrieren, sondern auch die internen Tabellen von Contao zu verändern und zu erweitern. Es ist also ein sehr mächtiges Werkzeug.

Auch wenn dieser Artikel für alle weiteren Kapitel sehr wichtig ist, wird es nicht möglich sein, alle Teile des DCA hier zu beleuchten. Wir werden in den weiteren Beiträgen noch auf zusätzliche Aspekte eingehen. Es sei aber schon einmal die Dokumentation des DCA empfohlen.

(Bitte auch in diesem Artikel wieder den Vendor-Namespace (oder entsprechenden Ordner) durch Euren eignen ersetzen und nicht Ctocb verwenden! Danke!)

Listing der Dateien

Hier wieder das Listing der verwendeten Dateien.

src/
└── Ctocb
    └── Example
        ├── Classes
        │   └── Contao
        │       └── Manager
        │           └── Plugin.php
        ├── Resources
        │   └── contao
        │       ├── config
        │       │   └── config.php
        │       ├── dca
        │       │   └── tl_testtable.php
        │       └── languages
        │           └── de
        │               ├── modules.php
        │               └── tl_testtable.php
        ├── composer.json
        └── CtocbExampleBundle.php

Ich werde ab jetzt nur noch auf das Manager Plugin und die composer.json eingehen, wenn dort neue Einträge vorhanden sind. Wenn man von den Standarddateien absieht, besteht unsere Erweiterung damit aus der DCA-Datei und der Sprachdatei der Tabelle tl_testtable.

Config

In der Datei /src/Ctocb/Example/Resources/contao/config/config.php geben wir unsere Tabelle für Contao bekannt.

<?php declare(strict_types=1);

$GLOBALS['BE_MOD']['module_test_group'] = [
    'module_test_table' => [
        'tables' => ['tl_testtable']
    ]
];

module_test_group ist unsere Gruppe, genau wie im letzten Artikel. Unser Menüpunkt heißt diesmal module_test_table. Die meisten einfachen Tabellen haben einen eigenen Menüpunkt. Anders ist es bei Kindtabellen, die über Ihre Elterntabelle aufgerufen werden. Aber diese behandeln wir später.

Mit tables geben wir an, welche Tabellen unsere Erweiterung nutzt. Es muss ein Array zugewiesen werden, da eine Erweiterung mehrere Tabellen verwenden kann (z. B. bei Kindtabellen).

DCA

Da das DCA sehr mächtig ist, ist es auch entsprechend umfangreich. Ich werde im weiteren Verlauf auf einzelne Abschnitte eingehen und diese separat zeigen. Zur besseren Übersicht wird am Ende das komplette DCA noch einmal als komplettes Listing zu sehen sein.

Die Datei muss unter /src/Ctocb/Example/Resources/contao/dca/tl_testtable.php gespeichert werden. Wichtig ist, dass sie den Namen der Tabelle trägt, da Contao sie sonst nicht finden kann. Der Grundaufbau sieht so aus:

<?php declare(strict_types=1);

$table = 'tl_testtable';

$GLOBALS['TL_DCA'][$table] = [
    // ... Abschnitte
];

Da der Name der Tabelle sehr oft verwendet wird, habe ich ihn in eine Variable ausgelagert.

DCA > config

Im Abschnitt config wird die Tabelle konfiguriert. Hier können u. a. auch load- und submit-Callbacks definiert werden (worauf wir in einem der nächsten Beiträge eingehen). Hier ein einfaches Beispiel für den Abschnitt config:

// Config
'config' => [
    'dataContainer'     => \Contao\DC_Table::class,
    'enableVersioning'  => true,
    'sql'               => ['keys' => ['id' => 'primary']]
],

Alle Details findet man in der Referenz des Handbuchs, im Abschnit Config.

DCA > list

Im Abschnitt list wird das Aussehen der Auflistung der Datensätze im Backend festgelegt. Unter sorting wird mit mode und flag festgelegt, wie die Datensätze sortiert werden sollen. Mit fields werden die Felder angegeben, nach denen sortiert wird. panelLayout legt das Aussehen der Such- und Filterleiste fest.

Im Abschnitt label wird die Benennung der Datensätze in der Liste konfiguriert. fields gibt die Felder an, die Angezeigt werden sollen und format das Format (s. sprintf für Details).

Daneben gibt es noch globale Operationen, die oben rechts angezeigt werden und die ganze Tabelle betreffen. Ein Beispiel hierfür ist "Mehrere Bearbeiten". Außerdem gibt es "noramle" Operationen, die für einen Datensatz durchgeführt werden (z. B. Bearbeiten, Kopieren, ...). In einem späteren Kapitel werden wir eine eigene Toggle-Funktion mit einer solchen Operation erstellen.

'list' => [
    'sorting' => [
        'mode'              => 1,
        'fields'            => ['title'],
        'panelLayout'       => 'sort,filter;search,limit',
        'flag'              => 1
    ],
    'label' => [
        'fields'            => ['title'],
        'format'            => '%s'
    ],
    'global_operations' => [
        'all' => [
            'label'         => &$GLOBALS['TL_LANG']['MSC']['all'],
            'href'          => 'act=select',
            'class'         => 'header_edit_all',
            'attributes'    => 'onclick="Backend.getScrollOffset();" accesskey="e"'
        ]
    ],
    'operations' => [
        'edit' => [
            'label'         => &$GLOBALS['TL_LANG'][$table]['edit'],
            'href'          => 'act=edit',
            'icon'          => 'edit.svg'
        ],
        'copy' => [
            'label'         => &$GLOBALS['TL_LANG'][$table]['copy'],
            'href'          => 'act=copy',
            'icon'          => 'copy.svg'
        ],
        'delete' => [
            'label'         => &$GLOBALS['TL_LANG'][$table]['delete'],
            'href'          => 'act=delete',
            'icon'          => 'delete.svg',
            'attributes'    => 'onclick="if(!confirm(\'' . $GLOBALS['TL_LANG']['MSC']['deleteConfirm'] . '\'))return false;Backend.getScrollOffset()"'
        ],
        'show' => [
            'label'         => &$GLOBALS['TL_LANG'][$table]['show'],
            'href'          => 'act=show',
            'icon'          => 'show.svg'
        ]
    ]
],

Will man bestimmte Opterationen nicht zur Verfügung stellen, z. B. weil man das Kopieren von Datensätzen unterbinden möchte, können die entsprechenden Operationen einfach aus dem Abschnitt entfernt werden.

DCA > palettes

Die Paletten beinhalten die Felder, die im Backend angezeigt werden sollen. In unserem Beispiel gibt es nur die Palette default, dies ist die Standardpalette. Paletten können aber auch anhand eines Feldes "gewählt" werden. Ein Beispiel ist die Tabelle tl_content, hier wird anhand des Typs des Inhaltselements die Palette gewählt und dann nur die entsprechenden Felder angezeigt.

'palettes' => [
    'default' => '{title_legend},title,content;'
]

Die Strings in den geschweiften Klammern, sind Gruppen. Diese werden im Backend grau dargestellt und können zu geklappt werden. Jeder Gruppe wird mit einem Semikolon nach den Feldern geschlossen. Die Felder werden einfach durch ein Komma getrennt. Es ergibt sich also folgendes Format:

'{Gruppe_01},Feld_01,Feld_02;{Gruppe_02},Feld_03,Feld_04;'

DCA > fields

Nun kommen wir endlich zu den eigentlichen Feldern unserer Tabelle. Ich habe in diesem Beispiel ein Feld für den Titel und eins für den Inhalt vorgesehen. Zusätzlich muss jede Tabelle noch das Feld id haben. Das Feld tstamp enthält das Änderungsdatum des Datensatzes. Ist es nicht vorhanden, löscht Contao die Datensätze, weil davon ausgegangen wird, dass sie nicht mehr benötigt werden. (Etwas vereinfacht ausgedrückt.) Das Feld title ist ein einfaches Textfeld, mit einer Maximallänge von 255. Das Feld content ist ein Textarea mit TinyMCE-Editor.

'fields' => [
    'id' => [
        'sql'                   => 'int(10) unsigned NOT NULL auto_increment'
    ],
    'tstamp' => [
        'sql'                   => "int(10) unsigned NOT NULL default '0'"
    ],
    'title' => [
        'exclude'               => true,
        'inputType'             => 'text',
        'eval'                  => ['mandatory'=>true, 'maxlength'=>255, 'tl_class' => 'w50'],
        'sql'                   => "varchar(255) NOT NULL default ''"
    ],
    'content' => [
        'exclude'               => true,
        'inputType'             => 'textarea',
        'eval'                  => ['mandatory'=>true, 'tl_class' => 'clr long', 'rte'=>'tinyMCE'],
        'sql'                   => "text NULL"
    ]
]

DCA komplett

Hier noch einmal das komplette DCA.

<?php declare(strict_types=1);

$table = 'tl_testtable';

$GLOBALS['TL_DCA'][$table] = [

    // Config
    'config' => [
        'dataContainer'             => \Contao\DC_Table::class,
        'enableVersioning'          => true,
        'sql'                       => ['keys' => ['id' => 'primary']]
    ],

    // List
    'list' => [
        'sorting' => [
            'mode'                  => 1,
            'fields'                => ['title'],
            'panelLayout'           => 'sort,filter;search,limit',
            'flag'                  => 1
        ],
        'label' => [
            'fields'                => ['title'],
            'format'                => '%s'
        ],
        'global_operations' => [
            'all' => [
                'label'             => &$GLOBALS['TL_LANG']['MSC']['all'],
                'href'              => 'act=select',
                'class'             => 'header_edit_all',
                'attributes'        => 'onclick="Backend.getScrollOffset();" accesskey="e"'
            ]
        ],
        'operations' => [
            'edit' => [
                'label'             => &$GLOBALS['TL_LANG'][$table]['edit'],
                'href'              => 'act=edit',
                'icon'              => 'edit.svg'
            ],
            'copy' => [
                'label'             => &$GLOBALS['TL_LANG'][$table]['copy'],
                'href'              => 'act=copy',
                'icon'              => 'copy.svg'
            ],
            'delete' => [
                'label'             => &$GLOBALS['TL_LANG'][$table]['delete'],
                'href'              => 'act=delete',
                'icon'              => 'delete.svg',
                'attributes'        => 'onclick="if(!confirm(\'' . ($GLOBALS['TL_LANG']['MSC']['deleteConfirm']  ?? '') . '\'))return false;Backend.getScrollOffset()"'
            ],
            'show' => [
                'label'             => &$GLOBALS['TL_LANG'][$table]['show'],
                'href'              => 'act=show',
                'icon'              => 'show.svg'
            ]
        ]
    ],

    // Palettes
    'palettes' => [
        'default'                   => '{title_legend},title,content;'
    ],

    // Fields
    'fields' => [
        'id' => [
            'sql'                   => 'int(10) unsigned NOT NULL auto_increment'
        ],
        'tstamp' => [
            'sql'                   => "int(10) unsigned NOT NULL default '0'"
        ],
        'title' => [
            'exclude'               => true,
            'inputType'             => 'text',
            'eval'                  => ['mandatory'=>true, 'maxlength'=>255, 'tl_class' => 'w50'],
            'sql'                   => "varchar(255) NOT NULL default ''"
        ],
        'content' => [
            'exclude'               => true,
            'inputType'             => 'textarea',
            'eval'                  => ['mandatory'=>true, 'tl_class' => 'clr long', 'rte'=>'tinyMCE'],
            'sql'                   => "text NULL"
        ]
    ]
];

Sprachdateien

Sprachdatei für die Tabelle

Damit unsere Felder richtig beschriftet werden, legen wir noch eine Sprachdatei für unsere Tabelle an. Sie muss ebenfalls den Namen der Tabelle tragen, damit Contao sie findet und wird unter /src/Ctocb/Example/Resources/contao/languages/de/tl_testtable.php gespeichert.

<?php declare(strict_types=1);
// Tablename
$table = 'tl_testtable';

// Elementname
$element = 'Datensatz';

// Fields
$GLOBALS['TL_LANG'][$table]['title']   = ['Title', 'Bitte geben Sie den Titel ein.'];
$GLOBALS['TL_LANG'][$table]['content'] = ['Inhalt', 'Bitte geben Sie den Inhalt ein.'];

// Legends (Einträge in palettes in den geschweiften Klammern)
$GLOBALS['TL_LANG'][$table]['title_legend']   = 'Title';

// Beschriftung der Buttons
$GLOBALS['TL_LANG'][$table]['new']        = ['Neuen ' . $element, 'Neuen ' . $element . ' anlegen'];
$GLOBALS['TL_LANG'][$table]['edit']       = [$element . ' bearbeiten', $element . ' mit der ID %s bearbeiten'];
$GLOBALS['TL_LANG'][$table]['copy']       = [$element . ' kopieren', $element . ' mit der ID %s kopieren'];
$GLOBALS['TL_LANG'][$table]['delete']     = [$element . ' löschen', $element . ' mit der ID %s löschen'];
$GLOBALS['TL_LANG'][$table]['show']       = [$element . ' anzeigen', 'Details des ' . $element . 'es mit der ID %s anzeigen'];

Die Übersetzung für die Felder kann im DCA mit 'label' angegeben werden. Dies ist aber nicht mehr nötig, wenn der Eintrag denselben Namen wie das Feld trägt. Für das Feld content sieht der Eintrag entsprechend so aus:

'label' => &$GLOBALS['TL_LANG'][$table]['content'],

Dies soll hier nur der Vollständigkeit halber erwähnt werden.

Sprachdatei für das Modul

Unsere Erweiterung stellt ein Backendmodul bereit. Dies ist der Menüpunkt im Backend, mit dem wir unsere Daten verwalten. Damit auch die Menüpunkte eine sinnvolle Beschriftung erhalten, legen wir noch eine Sprachdatei mit dem Namen /src/Ctocb/Example/Resources/contao/languages/de/modules.php und dem folgenden Inhalt an.

<?php declare(strict_types=1);
// Kategorie
$GLOBALS['TL_LANG']['MOD']['module_test_group'] = ['Daten', 'Daten'];
// Eintrag
$GLOBALS['TL_LANG']['MOD']['module_test_table']  = ['Testtabelle', 'Testtabelle'];

Datenbank aktualisieren

Da wir eine Tabelle erstellt haben, müssen wir nun noch die Datenbank aktualisieren. Dies geschieht entweder über den Manager, oder über die Konsole mit folgendem Befehl:

vendor/bin/contao-console contao:migrate

Fertig

Nun haben wir unsere erste Tabelle, in der wir Daten speichern können. Wir können die Datensätze anlegen, bearbeiten, ansehen und löschen. In den folgenden Kapiteln werden wir diese Tabelle erweitern und verbessern.

Im Backend könnte die Liste dann in etwa so aussehen:

Eigener Menuepunkt

Die Eingabemaske sähe dann so aus:

Eigener Menuepunkt