Contao: Bild für den eigenen Menüpunkt

von Patrick Froch (Kommentare: 2)

Heute komme ich zum vorerst letzten Teil der kleinen Produktdatenbank. In diesem Artikel wird sie so angepasst, dass man pro Produkt ein Bild hinterlegen kann, dies wird dann im Frontend ausgegeben. Der Beitrag baut auf die letzten letzten Posts auf und ich werde hier nicht mehr auf die Grundlagen eingehen. Es ist daher sinnvoll erst die anderen Beiträge der Reihe zu lesen:

DCA

Als erstes erweitern wir das DCA für die Produktdaten um die Felder für die Bilder. Zu Erinnerung, die Datei heißt system/modules/easy_extension/dca/tl_products.php.

<?php
/**
 * Set Tablename
 */
$strName = 'tl_products';


/**
 * Table tl_products
 */
$GLOBALS['TL_DCA'][$strName] = array
(

    // ... (Der Teil bis zu den Paletten bleibt unverändert!)

    // Palettes
    'palettes' => array
    (
        '__selector__'                => array(''),
        'default'                     => '{title_legend},title, price;{description_legend},description;{source_legend},singleSRC;{image_legend},alt,imgtitle,size,imagemargin,fullsize,caption;'
    ),

    // Subpalettes
    'subpalettes' => array
    (
        ''                            => ''
    ),

    // Fields
    'fields' => array
    (
        // ... (Auch die bestehenden Felder bleiben unverändert!)

        'singleSRC' => array
        (
            'label'                   => &$GLOBALS['TL_LANG'][$strName]['singleSRC'],
            'exclude'                 => true,
            'inputType'               => 'fileTree',
            'eval'                    => array('filesOnly'=>true, 'fieldType'=>'radio', 'tl_class'=>'clr', 'extensions'=>$GLOBALS['TL_CONFIG']['validImageTypes']),
            'sql'                     => "binary(16) NULL"
        ),
        'alt' => array
        (
            'label'                   => &$GLOBALS['TL_LANG'][$strName]['alt'],
            'exclude'                 => true,
            'search'                  => true,
            'inputType'               => 'text',
            'eval'                    => array('maxlength'=>255, 'tl_class'=>'w50'),
            'sql'                     => "varchar(255) NOT NULL default ''"
        ),
        'imgtitle' => array
        (
            'label'                   => &$GLOBALS['TL_LANG'][$strName]['imgtitle'],
            'exclude'                 => true,
            'search'                  => true,
            'inputType'               => 'text',
            'eval'                    => array('maxlength'=>255, 'tl_class'=>'w50'),
            'sql'                     => "varchar(255) NOT NULL default ''"
        ),
        'size' => array
        (
            'label'                   => &$GLOBALS['TL_LANG'][$strName]['size'],
            'exclude'                 => true,
            'inputType'               => 'imageSize',
            'options'                 => \Contao\System::getImageSizes(),
            'reference'               => &$GLOBALS['TL_LANG']['MSC'],
            'eval'                    => array('rgxp'=>'natural', 'includeBlankOption'=>true, 'nospace'=>true, 'helpwizard'=>true, 'tl_class'=>'w50'),
            'sql'                     => "varchar(64) NOT NULL default ''"
        ),
        'imagemargin' => array
        (
            'label'                   => &$GLOBALS['TL_LANG'][$strName]['imagemargin'],
            'exclude'                 => true,
            'inputType'               => 'trbl',
            'options'                 => $GLOBALS['TL_CSS_UNITS'],
            'eval'                    => array('includeBlankOption'=>true, 'tl_class'=>'w50'),
            'sql'                     => "varchar(128) NOT NULL default ''"
        ),
        'fullsize' => array
        (
            'label'                   => &$GLOBALS['TL_LANG'][$strName]['fullsize'],
            'exclude'                 => true,
            'inputType'               => 'checkbox',
            'eval'                    => array('tl_class'=>'w50 m12'),
            'sql'                     => "char(1) NOT NULL default ''"
        ),
        'caption' => array
        (
            'label'                   => &$GLOBALS['TL_LANG'][$strName]['caption'],
            'exclude'                 => true,
            'search'                  => true,
            'inputType'               => 'text',
            'eval'                    => array('maxlength'=>255, 'allowHtml'=>true, 'tl_class'=>'w50'),
            'sql'                     => "varchar(255) NOT NULL default ''"
        )
    )
);

In Zeile 20 wird die Palette erweitert und ab Zeile 33 werden die neuen Felder definiert. Es ist unbedingt darauf zu achten, dass die Einträge an der richtigen Stelle eingefügt werden.

Es müssen für das Bild eine ganze Reihe von Felder einfügt werden. Neben dem Feld für die Bildauswahl (singleSRC), werden noch die Felder für die alternativ Bezeichnung (alt), den Titel (imgtitle), die Größe (size), den Abstand (imagemargin), die Anzeige in einer Lightbox (fullsize) und die Bildunterschrift (caption) angelegt.

Leider gibt es an dieser Stelle eine kleine Abweichung vom Standard. Da wir in unserer Tabelle bereits ein Feld titel mit dem Namen des Produkts haben, müssen wir das Feld für den Title des Bildes imgtitle nennen. Wir müssen dies nachher in der Ausgabeklasse berücksichtigen. Ich werde später genauer darauf eingehen.

Da wir neue Felder angelegt haben, müssen wir nun die Datenbank aktualisieren!

Sprachdatei

Nun geben wir die Bezeichnungen für die Felder in die Datei system/modules/easy_extension/languages/de/tl_products.php ein.

<?php
/**
 * Set Tablename
 */
$strName = 'tl_products';

/**
 * Fields
 */
// ... (Die alten Einträge bleiben bestehen!)
$GLOBALS['TL_LANG'][$strName]['singleSRC']          = array('Quelldatei', 'Bitte wählen Sie eine Datei oder einen Ordner aus der Dateiübersicht.');
$GLOBALS['TL_LANG'][$strName]['alt']                = array('Alternativer Text', 'Hier können Sie einen alternativen Text für das Bild eingeben (&lt;em&gt;alt&lt;/em&gt;-Attribut).');
$GLOBALS['TL_LANG'][$strName]['imgtitle']           = array('Titel', 'Hier können Sie den Titel des Bildes eingeben (&t;em&gt;title&lt;/em&gt;-Attribut).');
$GLOBALS['TL_LANG'][$strName]['size']               = array('Bildgröße', 'Hier können Sie die Abmessungen des Bildes und den Skalierungsmodus festlegen.');
$GLOBALS['TL_LANG'][$strName]['imagemargin']        = array('Bildabstand', 'Hier können Sie den oberen, rechten, unteren und linken Außenabstand eingeben.');
$GLOBALS['TL_LANG'][$strName]['fullsize']           = array('Großansicht/Neues Fenster', 'Großansicht des Bildes in einer Lightbox bzw. den Link in einem neuem Browserfenster öffnen.');
$GLOBALS['TL_LANG'][$strName]['caption']            = array('Bildunterschrift', 'Hier können Sie einen kurzen Text eingeben, der unterhalb des Bildes angezeigt wird.');


/**
 * Legends
 */
// ... (Die alten Einträge bleiben bestehen!)
$GLOBALS['TL_LANG'][$strName]['source_legend']      = 'Dateien und Ordner';
$GLOBALS['TL_LANG'][$strName]['image_legend']       = 'Bild-Einstellungen';

Hier wurden einfach die Standardbezeichnungen aus dem Contao-Core verwendet.

Nun können wir im Backend die Produktbilder einpflegen.

Backend

Ausgabeklasse

Jetzt müssen wir noch die Ausgabeklasse überarbeiten. Ich beschränke mich in diesem Beispiel darauf, das Bild auf der Detailseite auszugeben. Die entsprechende Datei heißt system/modules/easy_extension/classes/elements/ContentProductDetails.php

<?php
namespace esit\easy_extension\classes\elements;

/**
 * Class ContentProductDetails
 * @package easy_extension\classes\elements
 */
class ContentProductDetails extends \ContentElement
{


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


    /**
     * Generate 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   = "### ContentProductDetails ###";
    }


    /**
     * Erzeugt die Ausgebe für das Frontend.
     * @return string
     */
    private function genFeOutput()
    {
        $intId = \Contao\Input::get('id');

        if ($intId) {
            $objProduct                 = $this->loadProduct($intId);
            $this->Template->objProduct = $objProduct;
            $this->insertImage($objProduct);
        }
    }


    /**
     * Lädt alle Einträge aus der Produktdatenbank
     * @return \Database\Result|object
     */
    private function loadProduct($intId)
    {
        $objDb      = \Contao\Database::getInstance();
        $strQuery   = "SELECT * FROM tl_products WHERE id = $intId";
        $objResult  = $objDb->execute($strQuery);
        return $objResult;
    }


    /**
     * Fügt ein Bild in das Template ein.
     * @param $objProduct
     */
    private function insertImage($objProduct)
    {
        $arrData                = $objProduct->fetchAssoc();
        $arrData['title']       = $arrData['imgtitle']; // nur nötig, da das Feld title bereits vorhanden ist!
        $arrData['singleSRC']   = \Contao\FilesModel::findByUuid($arrData['singleSRC'])->path;
        $this->addImageToTemplate($this->Template, $arrData);
    }
}

Die Methode genFeOutput wurde ab Zeile 54 etwas angepasst. Das Laden des Produkts wurde geändert und ein Aufruf für unsere neue Methode ergänzt (Zeile 56). Am Ende wurde dann die Methode für das Einfügen des Bildes in das Template erstellt (insertImage). In dieser Methode wandeln wir in Zeile 80 das Objekt vom Typ \Database\Result in ein Array um. In Zeile 82 erzeugen wir aus der Uuid des datenbankgestützten Dateisystems den Pfad zur Originaldatei.

Die Methode aus dem Contao-Core, die wir in Zeile 83 aufrufen, um das Bild ins Template einzufügen, erwartet den Titel zwingend im Feld title. In Zeile 81 speichern wir aus diesem Grund den Inhalt des Feldes imgtitle in das Feld title. Da es sich bei dem Array um eine lokale Kopie der Daten handelt, hat dies keinen Einfluss auf den übrigen Programmablauf.

Das Umspeichern des Titels ist nur nötig, weil wir im DCA bereits ein Feld mit diesem Namen hatten. Wir hätten ebenso gut das Feld "title" in "name" (o.ä.) umbenennen können.

Template

Hier bearbeiten wir das Template für die Detailseite system/modules/easy_extension/templates/ce_product_details.html5.

<div class="<?php echo $this->class; ?> block"<?php echo $this->cssID; ?><?php if ($this->style): ?> style="<?php echo $this->style; ?>"<?php endif; ?>>
    <!-- Überschrift aus dem Inhaltselement-->
    <?php if ($this->headline): ?>
        <<?php echo $this->hl; ?>><?php echo $this->headline; ?></<?php echo $this->hl; ?>>
    <?php endif; ?>

    <!-- Produktdaten -->
    <div class="product">
        <h2>
            <?php echo $this->objProduct->title; ?>
        </h2>

        <div class="price">
            Preis: <?php echo $this->objProduct->price; ?> €
        </div>

        <div class="description">
            <?php echo $this->objProduct->description; ?>
        </div>

        <!-- Produktbild -->
        <figure class="image_container"<?php if ($this->margin): ?> style="<?= $this->margin ?>"<?php endif; ?>>

            <?php $this->insert('picture_default', $this->picture); ?>

            <?php if ($this->caption): ?>
                <figcaption class="caption"><?= $this->caption ?></figcaption>
            <?php endif; ?>

        </figure>
    </div>
</div>

Auch hierbei handelt es sich um die normale Ausgabe aus dem Contao-Core. In Zeile 22 beginnt der Image-Container. Das eigentliche Bild wird in Zeile 24 ausgegeben. In Zeile 27 wird die Bildunterschrift ausgegeben (falls vorhanden).

Nun wird das Bild im Forntend ausgegeben und kann in einer Lightbox vergrößert werden.

Backend

Fazit

Die Bildverarbeitung gehört sicher schon zu den etwas anspruchsvolleren Themen in Contao, weshalb dieses Tutorial auch schon um einiges fordernder war, als die ersten Teile der Serie. Die nächsten Beiträge zum Thema Contao werden vermutlich wieder etwas leichter verdaulich.

Zurück

Einen Kommentar schreiben

Kommentar von Sebastian Schreier |

Hallo,

ich hätte ein paar Anmerkungen für dich.

Ich würde bei der insertImage-Funktion in der Ausgabeklasse ContentProductDetails.php noch eine if - Prüfung setzen, damit es nicht zu einer Fehlermeldung kommt, wenn kein Bild verlinkt ist, also:
if($arrData['singleSRC']){
$arrData['title'] = $arrData['imgtitle']; // nur nötig, da das Feld title bereits vorhanden ist!
$arrData['singleSRC'] = \Contao\FilesModel::findByUuid($arrData['singleSRC'])->path;

$this->addImageToTemplate($this->Template, $arrData);
}

Um die Ergänzung der Template-Datei ce_product_details.html5 würde ich noch ein <?php if ($this->picture): ?> setzen, damit der Bereich nur angezeigt wird, wenn ein Bild verlinkt wurde.

Ich hätte aber selbst noch eine Frage: wie müsste die Ausgabeklasse ContentProduct.php ergänzt werden, damit man sich das Bild auch bei der Listenansicht anzeigen lassen kann?

Beste Grüße

Antwort von Patrick Froch

Hallo Sebastian,

danke für Deine Anmerkungen. Es gibt sicher noch viele Dinge zu optimieren. Ich habe in diesen Grundlagenartikeln bewusst darauf verzichtet, da es nur darum geht die Zusammenhänge zu erläutern. Im Prinzip hast Du aber recht, im Produktivcode würde ich es auch anders machen.

Damit das Bild in der Liste angezeigt wird, benötigst Du eigentlich nur die Methode "insertImage" und die Ausgabe im Template.

Kommentar von Sebastian Schreier |

Hallo Patrick,

meine Frage bezog sich darauf, dass wenn ich die insertImage - Funktion in der Listenansicht beispielsweise so implementiere:
private function genFeOutput(){
  $objProducts = $this->loadProducts();

  if ($objProducts) {
    $this->Template->objProducts = $objProducts;
    $this->Template->jumpTo = $this->makeLink($this->jumpTo);
    $this->insertImage($objProducts);
  }
}

er mir in der Liste nur noch den letzten Listeneintrag anzeigt (zwar mit Bild, aber nur noch den letzten Listeneintrag). Kommentiere ich die insertImage - Funktion wieder aus, werden wieder alle Listeneinträge angezeigt. Hast du eventuell einen Tipp, was ich falsch mache?

Beste Grüße

Sebastian

Antwort von Patrick Froch

Hallo Sebastian,

okay es ist doch etwas komplizierter als ich dachte. Vielleicht mache ich mal ein extra Beitrag dazu. Du musst eine foreach-Schleife verwenden und das Bild für jeden Datensatz erstellen. In diesem Fall lädst Du ja alle Produkte (s. hier unter Ausgabeklasse -> loadProducts()). In ContentProductDetails->insertImage() verwendest Du dann fetchAllAssocc() statt fetchAssoc(). Dann kannst Du die Datensätze durchlaufen, den Pfad zum entsprechende Bild in dem Array speichern. Du kannst dann leider nicht die Methode addImageToTemplate() verwenden, da sie nur ein Bild einfügt. Du kannst aber ganz einfach die Bilder manuell im Template ausgeben.

Ich hoffe diese Kurzanleitung hilft Dir schon mal etwas weiter.

Viele Grüße,
Patrick