Beiträge der Kategorie TYPO3 v9

Datensatz-Titel in Breadcrumb-Navigation anzeigen

Vor TYPO9 war es zwar nicht einfacher, den Titel des eigenen Datensatzes in der Breadcrumb (oder auch Rootline) Navigation anzuzeigen, aber es gab Beispiele wie Sand am Meer. Falls man nun bei der Generierung der Menüs auf Data Processoren setzt, dann findet man (also ich per Google) einige Anfragen in Foren, jedoch keine gute Anleitung. Und wie immer in solchen Fällen schaue ich in die Extension news, die mir schon so häufig weitergeholfen hat. Diese Extension ist einfach auf dem neuesten Stand und wenn man mal nicht weiter weiß, dann lohnt sich immer ein Blick in diese Extension. Danke Georg!

Das ist eine kleine Anleitung, wie man die Breadcrumb um den Titel der eigenen Datensätze bereichert. Jede Extension ist anders implementiert, daher ist das nicht als Copy&Paste einfach verwendbar.

Als erstes muss man einen Data Processor anlegen, das kann entweder in der Extension sein, deren Datensätze man anzapft oder in einer anderen. In diesem Beispiel lege ich die Datei AddRecordToMenuProcessor.php an im Unterordner Classes/DataProcessing.

Irgendwo in TypoScript hat man nun die Page-Definition, die an irgendeiner Stelle so aussieht:

page.10 = FLUIDTEMPLATE
page.10 {
    dataProcessing {
        [...]
 
        30 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor
        30 {
            special = rootline
            special.range = 0|-1
            includeNotInMenu = 1
            as = menuBreadcrumb
        }
    }
}

Hinter die vorhandenen Data Processoren wird nun ein eigener angehängt:

page.10 = FLUIDTEMPLATE
page.10 {
    dataProcessing {
        [...]
 
        40 = Vendor\MyExtension\DataProcessing\AddRecordToMenuProcessor
        40.menus = menuBreadcrumb
    }
}

Nun arbeitet man in der angelegten Datei weiter.

namespace Vendor\MyExtension\DataProcessing;
 
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
 
class AddRecordToMenuProcessor implements DataProcessorInterface
{
 
    /**
     * @param ContentObjectRenderer $cObj
     * @param array $contentObjectConfiguration
     * @param array $processorConfiguration
     * @param array $processedData
     * @return array
     */
    public function process(
        ContentObjectRenderer $cObj,
        array $contentObjectConfiguration,
        array $processorConfiguration,
        array $processedData
    ) {
        if (!$processorConfiguration['menus']) {
            return $processedData;
        }
        if (!ExtensionManagementUtility::isLoaded('my_record_extension')) {
            return $processedData;
        }
        $record = $this->getRecord();
        if ($record) {
            $menus = GeneralUtility::trimExplode(',', $processorConfiguration['menus'], true);
            foreach ($menus as $menu) {
                if (isset($processedData[$menu])) {
                    $this->addRecordToMenu($record, $processedData[$menu]);
                }
            }
        }
        return $processedData;
    }
 
    public function addRecordToMenu($record, array &$menu) {
        // remove last element
        array_pop($menu);
 
        $menu[] = [
            'data' => $record,
            'title' => $record['name'],
            'active' => 1,
            'current' => 1,
            'link' => GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'),
            'isRecord' => true
        ];
    }
 
    /**
     * @return array
     */
    public function getRecord()
    {
        $vars = GeneralUtility::_GET('tx_myrecordextension_pluginname');
        if(!isset($vars['record']) {
            return;
        }
        $recordUid = (int)$vars['record'];
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
        $recordRepository = $objectManager->get(Vendor\MyRecordExtension\RecordRepository::class);
        $record = $recordRepository->findByUid($recordUid);
        $fields = ['name', 'title', 'email', 'phone'];
        $recordAsArray = [];
        foreach ($fields as $field) {
            $recordAsArray[$field] = $record->_getProperty($field);
        }
        return $recordAsArray;
    }
 
}

Die Funktion process ist notwendig, da man das Interface DataProcessorInterface implementiert. In meinem Fall hab ich den Data Processor in eine andere Extension packen müssen und daher brauche ich die Abfrage, ob die Extension geladen ist (Zeile 28). Ansonsten kann man sich das sparen.

Es wird versucht einen Datensatz der Extension zu holen und falls es funktioniert, wird das Menü um einen Eintrag mit den Daten des Datensatzes erweitert.

In der Funktion getRecord wird der Datesatz geholt. Georg nutzt in seiner news-Extension einen anderen Ansatz: nämlich mit ConnectionPool und QueryBuilder. Es hat den Vorteil, dass man ein Array und nicht wie in meinem Fall ein Object zurückbekommt. Daher brauche in dann die Konvertierung in ein Array. Es ist bestimmt nicht der beste Weg ein Objekt in ein Array zu konvertieren. Ich habe bisher nur nichts anderes gefunden, bin daher für Tipps in diese Richtung dankbar.

In der Funktion addRecordToMenu wird das Menü erweitert. Da habe ich auch einen etwas anderen Ansatz als in news. Dort wird das Menü erweitert. Ich schmeiße den letzten Eintrag raus und füge stattdessen meinen hinzu. Das hat aus meiner Sicht den Vorteil, dass man keinen Link zur Detailseite ohne Parameter hat. Den Ansatz aus News also dann verwenden, wenn die Detailansicht die Listenansicht überlädt. Falls man so wie in meinem Fall, eine separate Detailseite hat, die ohne Parameter keinen Sinn macht oder womöglich gar nicht funktioniert, dann muss man den Ansatz mit rausschmeißen-hinzufügen verfolgen.

TYPO3 Fluid-Templates und PhpStorm

Manchmal hab ich folgendes Problemchen: ich möchte z.B. falls Bedingung X eintrifft einen öffnenden Tag ausgeben und bei Bedingung Y einen schließenden Tag. PhpStorm fängt dann an, die Tags zu korrigieren, damit das Template aus seiner Sicht eine gültige Struktur hat. Ich habe bei meinem TYPO3-Kollegen Thomas Deuling einen einfachen Trick gesehen, wie man PhpStorm austricksen kann. Man schreibt die Tags einfach in !
Hier ein Beispiel für ein Media/Gallery Partial:

<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:ce="http://typo3.org/ns/TYPO3/CMS/FluidStyledContent/ViewHelpers" data-namespace-typo3-fluid="true">
<f:if condition="{gallery.rows}">
    <f:for each="{gallery.rows}" as="row">
        <f:if condition="{gallery.rows -> f:count()} > 1">
            <f:format.raw value="<div class='row'>"/>
        </f:if>
        <f:for each="{row.columns}" as="column">
            <f:if condition="{row.columns -> f:count()} > 1">
                <f:format.raw value="<div class='col'>"/>
            </f:if>
            <f:if condition="{column.media}">
                <f:render partial="Media/Type"
                          arguments="{file: column.media, dimensions: column.dimensions, data: data, settings: settings}"/>
            </f:if>
            <f:if condition="{row.columns -> f:count()} > 1">
                <f:format.raw value="</div>"/>
            </f:if>
        </f:for>
        <f:if condition="{gallery.rows -> f:count()} > 1">
            <f:format.raw value="</div>"/>
        </f:if>
    </f:for>
</f:if>
</html>

Es hat den kleinen Nachteil, dass man selbst den Code etwas schlechter lesen kann, aber wenigstens stimmt die Struktur und PhpStorm versucht nicht die Tags zu korrigieren.

Vorschau von Bildern aus Page Media im Backend

Es hat mich ein wenig gewundert, dass es keine Extension gibt, die das anbietet. Ich nutze das Feld “Media” unter Resources in den Seiteneingenschaften bei fast allen Projekten. Und gestern ist mir eingefallen, dass es ja eigentlich cool wäre, wenn man das eingepflegte Bild auch im Backend sehen könnte. In der TYPO3 Facebook-Gruppe hat die Nachfrage nach einer Extension nicht zum gewünschten Ergebnis geführt. Also beschloss ich (auch zu Übungszwecken) eine solche Extension selbst zu schreiben. Hier mein Kochrezept und Ergebnis.

Als erstes erstellt man einen Extensionordner mit einer ext_emconf.php. Meine Extension heißt übrigens np_pageresourcepreview.

In TYPO3 gibt es ja mittlerweile Hooks für so ziemlich alles. So auch einen um die Seite im Backend zu rendern. Man registrierert den Hook aus der neuen Extension in ext_localconf.php.

<?php
defined('TYPO3_MODE') || die('Access denied.');
 
if (TYPO3_MODE === 'BE') {
 
    // Hook into the page module
    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawHeaderHook'][$_EXTKEY] =
        \Npostnik\NpPageresourcepreview\Hook\PageHook::class . '->render';
}

Im Ordner Classes/Hook erstellt man eine Datei PageHook.php und schreibt folgendes rein:

<?php
namespace Npostnik\NpPageresourcepreview\Hook;
 
use TYPO3\CMS\Backend\Controller\PageLayoutController;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
 
class PageHook
{
 
    /**
     * @var StandaloneView
     */
    protected $view;
 
    public function render(array $params, PageLayoutController $parentObject)
    {
        $pageinfo = $parentObject->pageinfo;
        if($pageinfo['media'] == 0) {
            return '';
        }
        $fileRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\FileRepository::class);
        $fileObjects = $fileRepository->findByRelation('pages', 'media', $pageinfo['uid']);
 
        //load partial paths info from typoscript
        $this->view = GeneralUtility::makeInstance(StandaloneView::class);
        $this->view->setFormat('html');
 
        $resourcesPath = 'EXT:np_pageresourcepreview/Resources/';
        $this->view->setTemplatePathAndFilename($resourcesPath . 'Private/Templates/PageHook.html');
        $this->view->assign('files', $fileObjects);
        $this->view->assign('page', $parentObject->pageinfo);
        return $this->view->render();
    }
 
}

Und schließlich erstellt man im Ordner Resources/Private/Templates die Datei PageHook.html mit folgendem Inhalt:

<f:if condition="{page}">
    <f:for each="{files}" as="file">
        <f:image image="{file}" maxHeight="300" />
    </f:for>
</f:if>

Das ist schon alles. Installieren und feddich.