Beiträge getaggt mit typo3

Name von fe_user beim Speichern im Backend aus first_name und last_name setzen

Ich glaube mich daran zu erinnern, dass es mal in irgendeiner Registrierungsextension implementiert war. Man wünscht sich, dass der Name eines FE Benutzers (fe_user) sich automatisch aus Vor- und Nachname ergibt, andernfalls sind die Felder ja redundant.

Im Backend kann man mit einem Hook realisieren.

Erstmal muss man das Namensfeld des fe_user auf readOnly setzen, damit man es im BE nicht bearbeiten kann. Das geht am besten in Configuration/TCA/Overrides/fe_users.php der eigenen Extension:

$GLOBALS['TCA']['fe_users']['columns']['name']['config']['readOnly'] = 1;

Dann definiert man einen Hooks für das Backend-Formular: in ext_localconf.php folgendes einsetzen (My\Extension entsprechend ersetzen):

$GLOBALS ['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['extkey'] = 'My\Extension\Hook\TceMain';

Dann im Ordner Classes/Hook der Extension die Datei TceMain.php erstellen und darin eine Funktion wie folgt definieren:

<?php
namespace My\Extension\Hook;
 
use TYPO3\CMS\Backend\Utility\BackendUtility;
 
class TceMain
{
 
    /**
     * @param $status
     * @param $table
     * @param $id
     * @param array $fieldArray
     * @param \TYPO3\CMS\Core\DataHandling\DataHandler $pObj
     */
    public function processDatamap_postProcessFieldArray($status, $table, $id, array &$fieldArray, \TYPO3\CMS\Core\DataHandling\DataHandler &$pObj)
    {
        if ($table == 'fe_users') {
            if (array_key_exists('first_name', $fieldArray) || array_key_exists('last_name', $fieldArray)) {
                $oldRow = BackendUtility::getRecord('fe_users', $id);
                $firstName = (array_key_exists('first_name', $fieldArray)) ? $fieldArray['first_name'] : $oldRow['first_name'];
                $lastName = (array_key_exists('last_name', $fieldArray)) ? $fieldArray['last_name'] : $oldRow['last_name'];
                $fieldArray['name'] = $firstName . ' ' . $lastName;
            }
        }
    }
 
}

Dabei wird beim Speichern des Datensatzes der Name automatisch aktualisiert, falls first_name oder last_name bearbeitet wurden. Diese Funktion funktioniert in beiden Fällen: wenn man nur einen Datensatz vollständig bearbeitet oder wenn man mehrere Datensätze gleichzeitig bearbeitet.

Landauswahl aus static_info_tables in Extension femanager

Für die Benutzerregistrierung nutze ich die Extension femanager, die sich hervorragend konfigurieren lässt.

Im Extension Manual ist beschrieben, wie man die static_info_tables Tabellen als Quelle für die Länderauswahl einsetzen kann. Das funktioniert erstmal nur im Frontend. Wenn man die Templates so anpasst, wie im Manual beschrieben, dann landet der 3-stellige Iso Code des Landes in der Datenbank.

Nun fände ich es auch schön, wenn die entsprechende Auswahl auch im Backend benutzt werden würde. Und so kann man es konfigurieren:

Als erstes muss das TCA der fe_users Tabelle entsprechend umkonfiguriert werden. Dazu in einer eigenen Extension entweder in ext_tables.php oder (besser) in Configuration/Tca/Overrides/fe_users.php folgenden Code einfügen. Damit wird die Länderauswahl mittels itemProcFunc erstellt. In diesem Fall kann man leider nicht einfach ‚foreign_table‘ verwenden, da als Key automatisch die uid verwendet wird.

$GLOBALS['TCA']['fe_users']['columns']['country']['config'] = [
    'type' => 'select',
    'renderType' => 'selectSingle',
    'itemsProcFunc' => 'My\Extension\UserFunc\TcaProcFunc->staticInfoTablesItems',
    'maxitems' => 1
];

Dann legt man die Klasse TcaProcFunc in Classes/UserFunc an mit folgendem Inhalt:

<?php
namespace My\Extension\UserFunc;
 
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
 
class TcaProcFunc
{
 
    /**
     * @param array $config
     * @return array
     */
    public function staticInfoTablesItems($config)
    {
        $key = 'isoCodeA3';
        $value = 'shortNameLocal';
        $sortbyField = 'isoCodeA3';
        $sorting = 'asc';
 
        $objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\ObjectManager');
        $countryRepository = $objectManager->get('SJBR\StaticInfoTables\Domain\Repository\CountryRepository');
 
        $countries = $countryRepository->findAllOrderedBy($sortbyField, $sorting);
        $countryList = [];
        $countryList[] = ["", ""];
        foreach ($countries as $country) {
            /** @var $country \SJBR\StaticInfoTables\Domain\Model\Country */
            $countryList[] = [ObjectAccess::getProperty($country, $value), ObjectAccess::getProperty($country, $key)];
        }
        $config['items'] = $countryList;
        return $config;
    }
 
}

Der Code der Function ist aus dem ViewHelper GetCountriesFromStaticInfoTablesViewHelper aus der Extension femanager geklaut 🙂

Vorschau von Plugin-Einstellungen im Backend in TYPO3

Wenn man eingene Plugins implementiert und dabei mit Flexforms arbeitet, kann man bei einem eingesetzen Plugin im Backend nicht erkennen, welche Einstellungen im Flexform vorgenommen wurden. Es gibt in TYPO3 einen Hook, der es ermöglicht, sich in die Backend-Ansicht einzuklinken und diese Einstellungen dort auszugeben. Und so wird das implementiert:

Als erstes muss man den Hook registrieren in ext_localconf.php der eigenen Extension. Der Name der Klasse ist egal. Unter $pluginSignature gibt man den Namen des Plugin an, so wie man ihn registriert hat.

if (TYPO3_MODE === 'BE') {
    // Page module hook - show flexform settings in page module
    $extensionName = \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToUpperCamelCase($_EXTKEY);
    $pluginSignature = strtolower($extensionName) . '_pi';
    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$pluginSignature][$_EXTKEY] =
        'My\Extension\Hook\CmsLayout->getExtensionSummary';
}

Dann erstellt man die entsprechende Klasse. In meinem Fall die Klasse CmsLayout im Ordner Classes/Hook. Diese Klasse muss die Funktion getExtensionSummary haben, die als Parameter ein Array übergeben bekommt. Die Funktion gibt einen String zurück, der dann im Backend als Plugin-Zusammenfassung ausgegeben wird.

Dabei ist zu beachten, dass der Hook für alle Plugins aufgerufen wird, d.h. man muss selbst rausfinden, ob man sich in seinem Plugin befindet oder nicht.

Bei mir hat sich mittlerweile dieser Klassenaufbau etabliert:

namespace My\Extension\Hook;
class CmsLayout
{
    public function getExtensionSummary(array $params) {
        $result = null;
        if (strpos($params['row']['list_type'], 'myextension_') !== FALSE) {
            $pluginName = str_replace('myextension_', '', $params['row']['list_type']);
            if ($params['row']['pi_flexform'] != '') {
                $this->flexformData = \TYPO3\CMS\Core\Utility\GeneralUtility::xml2array($params['row']['pi_flexform']);
            }
            $methodName = 'bePreview' . ucfirst(GeneralUtility::underscoredToLowerCamelCase($pluginName));
            if (method_exists($this, $methodName)) {
                $result = $this->$methodName($params['row']);
            }
        }
        return $result;
    }
 
    protected function getFieldFromFlexform($key, $sheet = 'sDEF') {
        $flexform = $this->flexformData;
        if (isset($flexform['data'])) {
            $flexform = $flexform['data'];
            if (is_array($flexform) && is_array($flexform[$sheet]) && is_array($flexform[$sheet]['lDEF'])
                && is_array($flexform[$sheet]['lDEF'][$key]) && isset($flexform[$sheet]['lDEF'][$key]['vDEF'])
            ) {
                return $flexform[$sheet]['lDEF'][$key]['vDEF'];
            }
        }
 
        return NULL;
    }
 
    protected function bePreviewPi($row) {
        // whatever
        return '';
    }
}

In der Funktion getExtensionSummary werden die Flexform-Daten geparst, falls es sich um eines meiner Plugins handelt. Dann wird die Preview-Funktion aufgerufen, falls sie existiert. Damit spare ich mir if- oder switch-Abfragen.

Hinweis 8.5.: Ich habe mitbekommen, dass sich die Flexform-Definition in der TYPO3 Version 8 geändert hat. d.h. es müssten entsprechend Anpassungen an diesem Code vorgenommen werden.

Bug? Falsche Artikelauswahl auf der News-Seite

Ok, ich versuche mal mein Setup zu beschreiben.

Kategorien sehen ungefähr so aus:

  • Kategorie Eins
    • Subkategorie EinsEins
    • Subkategorie EinsZwei
  • Kategorie Zwe
    • Subkategorie ZweiEins
    • Subkategorie ZweiZwei
  • Kategorie Drei

Dann sind da natürlich News-Beiträge, die immer einer Hauptkategorie und (falls diese Unterkategorien hat) Unterkategorien zugewiesen sind. Pro Hauptkategorie gibt es jeweils eine Ausgabe-Seite, die in der Hauptspalte ein Plugin enthält, wo eingestellt ist, dass nur Beiträge einer Kategorie angezeigt werden. So weit passt alles.

Und auf einmal werden auf der Seite in Plugin, das eigentlich nur Beiträge der Kategorie „Kategorie Eins“ und Unterkategorien anzeigen soll, auch Beiträge der Kategorie Zwei angezeigt. Ich habe alles überprüft: Ist die Zuordnung im Artikel richtig gesetzt? Ist das Plugin auf der Seite richtig konfiguriert? Sieht alles gut aus. Hab schon angefangen an meinem Verstand zu zweifeln und an einen mysteriösen Bug in News geglaubt.

Lösung: Damit nur Artikel einer bestimmten Kategorie ausgegeben werden, muss im News-Plugin in der Kategorieauswahl „Show items with selected categories“ ausgewählt werden. Wenn man keine weiteren Einstellungen vornimmt, dann kann damit durch URL Parameter die Kategorieauswahl überschrieben werden. Wenn man RealURL verwendet, merkt man das nicht so schnell. Im RealURL Cache für diese Seite war nämlich ungefähr folgende URL eingetragen: seite/?tx_news_pi1[overwriteDemand][categories]=0 und die auch noch an erster Stelle. d.h. Wenn man die Seite aufruft, dann wird die sprechende URL so umgewandelt, dass die Kategorieauswahl aufgehoben wird.

Damit das nicht wieder passiert, kann man im News-Plugin das Überschreiben von Kategorien per URL deaktivieren: im Tab „Additional“ unter „Disable override demand“.

Auswahlgruppe „__Kein Umfluss__“ bei Bildausrichtung entfernen

Bei der Bildausrichtung gibt es in TYPO3 seit Ewigkeiten die Auswahlgruppe (optgroup) „Kein Umfluss“. Das blöde ist, dass alle neuen Items, die man über Tsconfig definiert, landen automatisch darin, egal wie die Zahlen sind. In den meisten Fällen wird der Kontext falsch sein. Daher hatte ich den Wunsch die Auswahlgruppe zu entfernen. Leider gibt es wohl schon länger einen Bug. Dabei wird das erste Element („Bild oben“) ebenfalls aus der Liste entfernt.

Ich habe eine andere Lösung gefunden – ich entferne die Optgroup in der ext_tables.php meiner Extension:

// remove __No wrap__ optgroup
unset($GLOBALS['TCA']['tt_content']['columns']['imageorient']['config']['items'][8]);

TYPO3 SQL Queries Debuggen

Wie man TYPO3 SQL Queries debuggen kann, ändert sich ja immer ein bisschen. In der Version 7.6. funktioniert das:

$parser = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser');
$queryParts = $parser->parseQuery($query);
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParts);

Diesen Codeschnipsel in die Repository-Funktion vor $query->execute() einsetzen.

Foundation Block-Grid mit Gridelements

Ich mag Foundation, unter anderem weil ich das Block Grid so toll finde. Und so kann man mit Gridelements ein Block-Grid im Frontend (mit einer hübschen Backend-Ausgabe) realisieren.

Zunächst man braucht man ein Gridelement mit nur einer Content-Spalte. Da schmeißt man dann alle Inhaltselemente rein und sie werden automatisch in Spalten ausgegeben. In diesem Fall hab ich das Gridelement in einer Datei konfiguriert.

tx_gridelements.setup.blockgrid {
	title = Blocks
	description = Inhalte automatisch auf mehrere Spalten verteilen
	icon = EXT:my_extension/Resources/Public/Icons/columns-3.png
	flexformDS = FILE:EXT:my_extension/Configuration/FlexForms/Gridelements/Blockgrid.xml
	config {
		colCount = 1
		rowCount = 1
		rows {
			1 {
				columns {
					1 {
						name = Inhalt
						colPos = 55
					}
				}
			}
		}
	}
}

Im Flexform habe ich zwei Felder konfiguriert – large, medium und small. Darin gibt man die Anzahl der Elemente für die jeweilige Auflösung an. Default sind es 3 Large, 2 Medium und 1 Small.

Die Ausgabe erfolgt durch ein Fluid-Template. Wie die Konfiguration dazu aussieht, findet man schnell im Internet – einfach nach „gridelements fluid“ suchen. Damit ich keinen eigenen ViewHelper schreiben muss, um auf variable Array-Indizes zuzugreifen, habe ich Vhs verwendet. Jedes Content-Element aus der Spalte wird in <div class="column"></div> gewrappt.

{namespace v=FluidTYPO3\Vhs\ViewHelpers}
<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">
<div class="row small-up-{data.flexform_large} medium-up-{data.flexform_medium} large-up-{data.flexform_large}">
    <f:for each="{data.tx_gridelements_view_children}" as="content">
        <div class="column">
            <f:format.raw>
                <v:variable.get name="data.tx_gridelements_view_child_{content.uid}" />
            </f:format.raw>
        </div>
    </f:for>
</div>
</html>

So weit passt schonmal das Frontend. Im Backend kann man ein wenig mit CSS tricksen. Ich habe bewusst als colPos 55 gewählt. Diese Zahl wird ins Template reingeschrieben, und macht es so möglich, ein spezielles CSS darauf anzuwenden. Man definiert also ein Backend CSS und schreibt da folgendes rein (Scss):

.t3-page-ce-wrapper[data-colpos='55'] {
  .t3-page-ce[data-uid] {
    width: 46%;
    float: left;
 
    &:nth-child(2n) {
      clear: both;
    }
  }
}

Damit werden die Content Element im Backend in zwei Spalten dargestellt. Theoretisch kann man da auch Breakpoints definieren, der Phantasie sind keine Grenzen gesetzt.

So was wie MenuItemProcFunc in Content Element „Menü“ mit Fluid Styled Content

Früher, in den Zeiten von css_styled_content konnte man in das Inhaltselement „Menü“ mittels itemArrayProcFunc eigene Menü-Items einhängen. Das funktioniert nach wie vor mit HMENU und sieht so aus:

1 = TMENU
1 [...]
2 < .1
2 {
	wrap = <ul>|
	itemArrayProcFunc = My\Extension\Hooks\MenuItemArrayProcessor->process
}

In der Funktion kann man dann z.B. Items aus der eigenen Extension ins Menü hängen, damit es so aussieht als wären es Seiten.

Das hat mit css_styled_content gut funktioniert, da dort das Rendering des Inhaltselemente (unter anderem Menü) ebenfalls mit TypoScript gemacht wurde. Also wenn man den TypoScriptObjectBrowser richtig bedienen kann, dann findet man auch schnell raus, wo man den Code einsetzen muss.

Nun stand ich vor einem Problem (nein, falsch – Herausforderung). In fluid_styled_content ist das Rendering der Content Element komplett über Fluid. So gibt es z.B. einen ViewHelper, der für „Menü ausgewählter Seiten“ die Seiten holt. Die Ausgabe geschieht dann mit Fluid. Nix TypoScript!

Ich habe folgendes implementiert: Falls eine bestimmte Seiten-ID in der Liste vorkommt (es ist meine Ziel-Detailseite), dann schmeiss ich einen weiteren ViewHelper an, der mit die Items mit Titel und Parameter ermittelt. Das Rendering dann wieder mit Fluid.

(mehr …)

News-Kategorie auf Detailansicht in Kategoriebaum hervorheben

Folgendes Setup: Seite mit News-Plugins: Links ein Kategoriebaum, rechts die dadurch gefilterte Listenansicht. Dann gibt es da noch eine Detailseite, auf der dann das Plugin für die Detailansicht ist. Nun will mein Kunde auf der Detailansicht links ebenfalls den Kategoriebaum haben und dabei soll die aktuelle Kategorie der News hervorgehoben sein. Und das funktioniert so:

Die Extension news ist ziemlich genial – es gibt dort Signals, die aus dem Controller aufgerufen werden und so das Manipulieren von Daten möglich machen. Dann muss man nur eine eigene Klasse registrieren (ext_localconf.php)

$signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
$signalSlotDispatcher->connect(
    'GeorgRinger\\News\\Controller\\CategoryController',
    'listAction',
    'My\\Extension\\Signals\\CategoryController', // fully your choice
    'listActionSlot', // fully your choice
    TRUE
);

Und dann entsprechend die Funktion implementieren. In meinem Fall war es etwas komplizierter, da jede News mehreren Kategorien zugewiesen wurde und die Kategorien verschachtelt waren. Da ich nur eine Kategorie hervorheben kann, habe ich mich dann dafür entschieden, die Kategorie ohne Unterkategorien zu wählen.

/**
 * get current news item category and set in overwrite demand
 *
 * @param array $categories
 * @param array $overwriteDemand
 * @param array $demand
 * @param array $extendedVariables
 */
public function listActionSlot($categories, $overwriteDemand, $demand, $extendedVariables)
{
	$newsArguments = GeneralUtility::_GET('tx_news_pi1');
	$newsUid = (int)$newsArguments['news'];
 
	if($newsUid > 0) {
		/** @var $db TYPO3\CMS\Core\Database\DatabaseConnection */
		$db = $GLOBALS['TYPO3_DB'];
 
		$result = $db->exec_SELECT_mm_query(
			'sys_category.*',
			'sys_category',
			'sys_category_record_mm',
			'tx_news_domain_model_news',
			'AND tablenames = \'tx_news_domain_model_news\' AND fieldname=\'categories\' AND uid_foreign = '.$newsUid
		);
		$parentUids = array();
		$uids = array();
		while($row = $db->sql_fetch_assoc($result)) {
			$parentUids[] = $row['parent'];
			$uids[] = $row['uid'];
		}
		/* find category that is not a parent */
		$categoryUid = array_shift(array_diff($uids, $parentUids));
		return [
			'categories' => $categories,
			'overwriteDemand' => ['categories' => $categoryUid],
			'demand' => $demand,
			'extendedVariables' => $extendedVariables
		];
	}
	return [
		'categories' => $categories,
		'overwriteDemand' => $overwriteDemand,
		'demand' => $demand,
		'extendedVariables' => $extendedVariables
	];
}

Und so klappts dann bei mir 🙂