TYPO3 Order by Uid List in Repository

Ich habe ein Flexform mit einem Feld, in dem man Datensätze auswählen kann, die man berücksichtigen möchte. Nun sollen diese Datensätze in der Reihenfolge selektiert werden, die man im Flexform gewählt hat. Wie das Flexform zu konfigurieren ist, spare ich mir hier. Die Abfrage im Repository ist jedoch interessant. Theoretisch war es früher möglich (vor Doctrine) mit einem ORDER BY FIND_IN_SET die Elemente direkt in der richtigen Reihenfolge aus der Datenbank zu holen. Vielleicht geht es doch immer noch irgendwie (immerhin habe ich es geschafft, Elemente zufällig zu sortieren), in meinem Beispiel soll es eine PHP Funktion tun.

Und das ist mein Code: kurz und fein, wie ich finde.

public function findByUidOrdered($uidList = [])
{
    $query = $this->createQuery();
    if(count($uidList) > 0) {
        $query->matching($query->in('uid', $uidList));
    }
    $result = $query->execute()->toArray();
    usort($result, function($a, $b) use ($uidList) {
        $aIndex = array_search($a->getUid(), $uidList);
        $bIndex = array_search($b->getUid(), $uidList);
        return ($aIndex < $bIndex) ? -1 : 1;
    });
    return $result;
}

Funktioniert wunderbar in TYPO3 8.7.

TYPO3 Order by Random im Repository

Oder auch: Datensätze in zufälliger Reihenfolge ausgeben

Es kommt schonmal vor, dass man Datensätze aus dem Repository in zufälliger Reihenfolge ausgeben möchte. In meinem Fall habe ich Kundenzitate und möchte sie gerne zufällig sortiert in einem Slider ausgeben. Meine Recherche ergab, dass ORDER BY RAND() nicht geht seit Doctrine in TYPO3 eingezogen ist, diverse Links zu Diskussionen hier und da. Ich bin stur: es muss doch gehen. Da habe ich diesen Forumsbeitrag entdeckt, wo jemand es mit TypoScript hinbekommen hat, in TYPO3 8 wohlgemerkt. Da TypoScript ja auch von Doctrine verarbeitet wird, geht es also doch, man muss nur etwas um die Ecke denken.

Meine Lösung sieht wie folgt aus: Query mit Doctrine zusammenbauen, zusätzliches Feld einfügen das einen Zufallswert enthält und nachdem sortiert wird, Ergebnisse holen und mit dem DataMapper in Objekte transformieren.

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
/**
 * @param int $limit
 */
public function findRandom($limit)
{
    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->getTableName());
    $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
    $records = $queryBuilder
        ->select('*')
        ->addSelectLiteral('rand() AS random_sort')
        ->from($this->getTableName())
        ->orderBy('random_sort')
        ->setMaxResults($limit)
        ->execute()
        ->fetchAll();
    $dataMapper = GeneralUtility::makeInstance(DataMapper::class);
    $result = $dataMapper->map($this->objectType, $records);
    return $result;
}
 
/**
 * Return the current tablename
 *
 * @return string
 */
protected function getTableName()
{
    $dataMapper = GeneralUtility::makeInstance(DataMapper::class);
    $tableName = $dataMapper->getDataMap($this->objectType)->getTableName();
    return $tableName;
}

Funktionert wunderbar in TYPO3 8.7.

TYPO3 E-Mail-Adressen in JsonView verschlüsseln

Ich leistete Support bei einem Projekt (d.h. ich hatte es nicht selbst programmiert) und hatte mit einer Ansprechpartner-Liste zu tun, die mit Ajax (JSON-Format) nachgeladen wurde. Bei dieser Site sollte die TYPO3 E-Mail-Adressverschlüsselung aktiviert werden. Für die Ansprechpartner-Liste war ein Controller geschrieben, der die Ansprechparter aus dem Repository holte und ins JSON-Format umwandelte, ein Fluid-Template wurde nicht benutzt. Als ich die E-Mail-Adressverschlüsselung aktiviert hatte (hier nur ein Beispiel), griff sie an der Stelle nicht und die E-Mail-Adressen wurden unverschlüsselt ausgeliefert.

config.spamProtectEmailAddresses = 2
config.spamProtectEmailAddresses_atSubst = (at)

Da ich nicht viel umbauen wollte, hatte ich die E-Mail-Adresserschlüsselung in den Controller implementiert. Wenn man sich den Fluid EmailViewHelper naschaut, dann ist die entscheidende Zeile eigentlich folgende:

list($linkHref, $linkText) = $GLOBALS['TSFE']->cObj->getMailTo($email, '');

Da ich letztens über die JsonView gelesen hatte, ließ mir die Aufgabe keine Ruhe. Wie könnte man die JsonView verwenden und dabei die in TYPO3 eingestellte E-Mail-Adressverschlüsselung beibehalten?

Ich habe eine kleine Demo-Extension, in der ich Personen-Datensätze verwalte mit Name, Email und noch ein paar weiteren Feldern. Im AjaxController aktiviere ich die JsonView und konfiguriere sie. In der Konfiguration gebe ich 2 Felder an, die nicht in der Datenbank existieren. Dann füge ich dem Model getter-Funktionen für diese zwei Felder hinzu.

Controller

class AjaxController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
 
    protected $defaultViewObjectName = JsonView::class;
 
    /**
     *
     */
    public function personListAction()
    {
        $jsonViewConfiguration = [
            'persons' => [
                '_descendAll' => [
                    '_exclude' => ['pid'],
                    '_only' => ['name', 'email', 'emailHref', 'emailText']
                ]
            ]
        ];
        $personRepository = $this->objectManager->get(PersonRepository::class);
        $persons = $personRepository->findAll();
        $this->view->setVariablesToRender(['persons']);
        $this->view->setConfiguration($jsonViewConfiguration);
        $this->view->assign('persons', $persons);
    }
}

Model

class Person extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
    [...]
    /**
     * @return string
     */
    public function getEmailHref()
    {
        list($linkHref, $linkText) = $GLOBALS['TSFE']->cObj->getMailTo($this->email, '');
        return $linkHref;
    }
 
    /**
     * @return string
     */
    public function getEmailText()
    {
        list($linkHref, $linkText) = $GLOBALS['TSFE']->cObj->getMailTo($this->email, '');
        return $linkText;
    }
}

JSON Output

[
{
"email": "bruce@wayne-enterprises.com",
"emailHref": "javascript:linkTo_UnCryptMailto('ocknvq,dtwegBycapg\\/gpvgtrtkugu0eqo');",
"emailText": "bruce(at)wayne-enterprises.com",
"name": "Bruce Wayne"
},
[...]

Wirklich glücklich bin ich mit dieser Lösung nicht. Ich habe das Gefühl, dass ich Darstellungslogik, die eigentlich in den View gehört, im Model unterbringe. Mir würde es besser gefallen, wenn man Prozessoren für Objekte definieren könnte. Leider gibt die Konfiguration des JsonView das im Moment nicht her.

Fluid f:comment-ViewHelper und Inline JavaScript

In Vorbereitung auf die TYPO3 Developer Prüfung schaue ich mir gerade vieles in TYPO3 genauer an. So wie im Moment die Standard Fluid-ViewHelper. Dabei bin ich beim f:comment ViewHelper hängen geblieben. Die Dokumentation beschreibt da einen Fall, denn ich so nicht reproduzieren konnte. Mein TYPO3 8.7.19 verhält sich so:

Inhalte gewrappt in f:comment werden nicht ausgegeben – weder im Quellcode, noch sichtbar.

<f:comment>
    <p>Wrapped in f:comment: Anzahl Einträge {data -> f:count()} </p>
</f:comment>

Inhalte gewrappt in CDATa werden ebenfalls nicht ausgegeben. Das ist der Dokumentation noch anders beschrieben.

<![CDATA[<p>Wrapped in CDATA: Anzahl Einträge {data -> f:count()} </p>]]>

Es gibt allerdings einen neuen ViewHelper f:format.cdata, der Inhalt bei der Ausgabe in CDATA wrappt. Eigentlich toll.

<f:format.cdata><p>Wrapped in f:format.cdata: Anzahl Einträge {data -> f:count()} </p></f:format.cdata>

Problem dabei ist, dass dann folgendes im Quellcode ausgegeben wird:

<![CDATA[<p>Wrapped in f:format.cdata: Anzahl Einträge 10 </p>]]>

Der Browser (in meinem Fall Chrome) stellt auf der Seite ]]> dar. Wünschenswert wäre, dass auf der Seite gar keine Ausgabe erfolgt. Ich habe mich noch nie ausführlich mit CDATA beschäftigt, weiß daher nicht, ob es an f:format.cdata oder meinem Browser liegt.

Und nun zum nächsten Problemchen: Inline JavaScript. Ich hatte etwas recherchiert und die Vorschläge, wie man Fluid vom Parsen von Inline-JavaScript abhält, beziehen sich alle auf Wrappen in CDATA. Was ja eben nicht funktioniert. Also f:format.cdata ViewHelper benutzen:

<script type="text/javascript">
    <f:format.cdata>
        function sayHello() {
            alert("hello world");
        }
        sayHello();
    </f:format.cdata>
</script>

Allerdings wirft der obige Code einen JavaScript-Fehler. Der Browser stört sich an dem < am Beginn von CDATA. Eine funktionierende Lösung sieht damit so aus:

<script type="text/javascript">
    //<f:format.cdata>
        function sayHello() {
            alert("hello world");
        }
        sayHello();
    //</f:format.cdata>
</script>

Gibt es denn eine Möglichkeit, einen Block auszugeben, den aber von Fluid nicht parsen zu lassen? Wofür könnte man es brauchen? Ich werde weiter schauen…

Eigene Fehlermeldungen für Extbase-Validatoren

(Custom error message for extbase validators)

Da ich auf die schnelle nichts gefunden habe und es mir selbst zusammengereimt hab, schreibe ich es hier besser auf. Um die Standard-Fehlermeldungen der Extbase-Validatoren zu ändern, schreibt man folgendes ins Setup:

config.tx_extbase._LOCAL_LANG.default {
    validator.notempty.null = This is a custom error message 1
    validator.notempty.empty = This is a custom error message 2
}

Damit werden die Fehlermeldungen global für die Sprache geändert. Den Key (validator.notempty.null) findet man im Validator:

class NotEmptyValidator extends AbstractValidator
{
	protected $acceptsEmptyValues = false;
 
	public function isValid($value)
	{
		if ($value === null) {
			$this->addError(
				$this->translateErrorMessage(
					'validator.notempty.null',
					'extbase'
				), 1221560910);

Flux und Inhaltselemente in News

In der Extension “News” gibt es ein schönes Feature. Dabei können News-Beiträge mit Inhaltselementen beliebig erweitert werden. In meinem aktuellen Projekt wurde die Seite mit Flux umgesetzt und dafür wurden alle Standard-Inhaltselemente per TSconfig deaktiviert. Damit man die “normalen” Text-Elemente in den News verwenden kann, muss man im TSconfig der News-Storage-Seite folgendes hinterlegen:

# reset remove items
TCEFORM.tt_content.CType.removeItems = html
# allow only text
TCEFORM.tt_content.CType.keepItems = text

Wichtig ist zunächst die removeItems zurückzusetzen (removeItems = black list). Diese Einstellung überschreibt andernfalls alles andere. Mit keepItems können Elemente angegeben werden, die stehen bleiben, während der Rest entfernt wird. (keepItems = white list)

Laravel – erster Eindruck

Soeben bin ich fast fertig mit einer Applikation, mit der ich Playlisten für meine Les Mills Kurse erstellen kann. Ich habe mich entschieden, die Applikation in Laravel zu schreiben, weil es spannend ist, mal was neues kennenzulernen (Laravel ist beliebste PHP-Framework im Moment) und weil ich TYPO3 dafür zu viel fand.

Als Einstieg in Laravel habe ich mir “Laravel From Scratch” Tutorials angeschaut. Die Videos fand ich echt gut, auch wenn der Entwickler verdammt schnell tippt und Fenster wechselt. Ich musste immer wieder mal stoppen und sortieren, wo er gerade ist. Mir hat gut gefallen, dass er ziemlich schnell eine Ausgabe erzeugt, auch wenn es nicht immer der eleganteste Weg ist. In den späteren Tutorials passt er es auch an und zeigt einen besseren Weg. z.b. werden am Anfang die Ausgaben direkt in die Routing-Datei geschrieben. Später erzeugt er auch View, Models und Controller.

Etwas verwirrend an Laravel finde ich die Migrations. Zum einen das Wording, zum anderen, dass man Felder nicht nach dem Erstellen der Tabelle hinzufügen kann. Wenn ich also bereits eine Tabelle angelegt und befüllt habe, brauche ich eine neue Migration, die ein Feld zu der Tabelle hinzufügt. Alternativ kann ich durch Rollback meine Tabelle löschen, die Migration anpassen und neu erstellen.

Gut gefällt mir das Routing. Gerade dann, wenn man mit Route::resource() automatisch Routen mit einem Controller verknüpfen kann. Zusammen mit CRUD kann man so schnell Funktionen fürs Erstellen und Bearbeiten implementieren. Ebenfalls positiv finde ich Blade-Templates. Mein Lieblings-Templating-Framework ist nach wie vor Flow, mein Horror ist Smarty. Blade ist ein guter Kompromiss zwischen “sieht aus wie HTML” und “PHP verwendenen in Templates”. In den Tutorials schreibt Jeffrey seine Formulare mit <form ...> in die Templates. Dann fand ich einige Beispiele mit Form::open, was mir natürlich besser gefällt. Seit Laravel 5.5 ist der Form-Helper ausgelagert worden. Nachdem ich das herausgefunden hatte, schrieben sich die Formulare noch schneller. Wenn man das Prinzip mit den View-Helpern verstanden hat, kann man relativ schnell eigene implementieren. So brauchte ich z.B. einen View-Helper, der mir aus meinem Rating (1 bis 5) Sternchen malt. Wäre auch mit einem Include gegangen, ist aber so noch eleganter.

Ebenfalls gut lassen sich Relationen abbilden. Mit hasMany, belongsTo kann man die Verknüpfungen zwischen Models definieren. Ebenfalls eine gute Idee sind die Scopes. Man definiert im Model einen Scope mit einer Einschränkung und kann im Controller oder von einem anderen Model auf den Scope zugreifen. So habe ich in meiner Applikation Scopes für Tracks definiert (Standard mit bonus=0 und Bonus mit bonus=1) und kann die Tracks in der Release-Einzelansicht in zwei verschiedenen Bereichen ausgeben.

Der Vorteil eines gut durchdachten Frameworks ist, dass man relativ schnell eine funktionierende Applikation bekommt. Auch wenn ich mich erst seit 2 Wochen mit Laravel beschäftige, habe ich meine Plan umsetzen können und die Applikation funktioniert hervorragend. Fazit: TYPO3 bleibt nach wie vor mein Steckenpferd, Laravel hat mir jedoch gut gefallen und ich könnte mir vorstellen, mich bei Bedarf da weiter einzuarbeiten.

LesMixer – Playlists für LesMills Kurse

Schon von ca. einem Jahr hatte ich die Idee zu einer (für mich sinnvollen) Anwendung. Als Hobby unterrichte ich Kurse im Fitnessstudio: BODYPUMP und BODYCOMBAT. Beides sind Fitnessprogramme von Les Mills. Das besondere: sie sind vorchoreografiert. Alle 3 Monate gibt es eine neue Release (neue Musik und neue Bewegungen), diese lerne ich dann und unterrichte sie. Nach ca. 6 bis 8 Wochen sorge ich für Abwechslung in den Kursen, in dem ich “mixe”, d.h. ich ersetze Tracks aus dem aktuellen Programm durch ältere Tracks. Beim Austauschen den Tracks gibt es einiges zu beachten, da der programmtypische Stundenablauf beibehalten werden soll. Jeder Track hat eine Funktion. So ist bei BODYPUMP der 2.Track immer Squats (d.h. es werden die Beine trainiert). Beim Austauschen des Tracks kann ich dann nur einen Squat-Track aus einer älteren Release verwenden. Dann sollten die ausgetauschten Lieder gemeinsam auch musikalisch Kontraste bilden und die Länge aller Lieder sollte zusammen eine bestimmte Zeit nicht überschreiten.

Bis vor kurzem mixte ich “manuell”. Ich überlegte mir, welche Lieder ich machen könnte, hörte kurz rein (klickte mich vorher durch Ordner) und überprüfte die Gesamtlänge der Auswahl. Also dachte ich mir, da kann ich doch eine kleine Anwendung schreiben, die mir die Liedauswahl erleichtert. Zunächst braucht man eine Datenbasis: ich muss bisherige Releases einpflegen können. Danach wäre es noch praktisch, diese Daten durchsuchen zu können: nach Künstler, Musiktitel oder Bewegungen.

Ich hätte es auch in TYPO3 implementieren können. Manchmal möchte ich meinen Horizont erweitern und ein neues Framework oder eine neue Anwendung kennenlernen. Das geht am besten mit Learning-by-Doing. Von einigen habe ich schon gehört, wie toll Laravel ist und wollte es selbst ausprobieren. Nachdem ich mir einen kurzen Überblick über Laravel verschafft habe (Video-Tutorials), ging es an die Umsetzung meiner Applikation names “LesMixer”.

Um mir die Datenpflege zu erleichtern, habe ich einige sinnvolle Funktionen eingebaut. Da ich die Musik in Ordner abgelegt hatte, konnte ich automatisch beim Auswählen eines Programms und einer Release die Mp3-Dateien im Ordner einlesen und zur Auswahl anbieten. Sobald eine Datei ausgewählt wird, werden Künstler, Titel und Länge (Mp3-Metadaten) und in die entsprechenden Felder eingesetzt. d.h. Tracks für eine neue Release einzupflegen ist denkbar einfach. Sobald die Tracks eingepflegt sind, kann man sie direkt in der Applikation abspielen. Die Suche oben durchsucht Künstler und Track-Titel.

Eine Playlist (Mix) zu erstellen, ist einfach. Sobald man ein Programm ausgewählt hat, werden die Tracktypen gelistet (der Ablauf ist ja immer gleich) und man kann für jeden Tracktyp einen konkreten Track auswählen. Irgendwann möchte ich diesen Vorgang automatisieren. Da ich ein Rating eingebaut habe, könnte ich mir vorstellen, in Zukunft eine Playlist generieren zu lassen unter Berücksichtigung bestimmter Einschränkungen.

Pagination Widget im Backend anpassen

Ich habe ein Modul implementiert, in dem bestimmte Datensätze gelistet werden. Über der Auflistung hatte ich einen Filter für die Datensätze implementiert und für die Paginierung das Fluid BE-Pagination Widget verwendet. Nun musste ich erstaunt feststellen, dass das BE-Paginate-Widget (be.widget.paginate) keine weiteren Argumente mitschleppen kann und es auch nicht konfigurierbar ist. Das bedeutet: beim Blättern gehen die Einstellungen aus dem Filter verloren.

Ich habe es hinbekommen mit einem eigenen ViewHelper und einem eigenen Template fürs Pagination-Widget.

(mehr …)

Tags: ,

Geschrieben in TYPO3 | Kommentare deaktiviert für Pagination Widget im Backend anpassen

Magnific Popup: springen von fix positioniertem Content

Wenn man Maginific-Popup zum Vergrößern von Bilder einsetzt, dann springen die fix positionierten Elemente beim Öffnen des Popups. Warum? Magnific Popup entfernt das Overflow Attribut des html-Elements und fügt stattdessen ein margin-right hinzu, das der Breite der Scrollbalken entspricht. Das schaltet das Scrolling der Seite aus. Ein Problem stellen die fix positionierten Elemente dar: sie springen leicht beim Öffnen des Popups. Wenn man bei fix positionierten Elementen diese entweder von rechts ausrichtet oder die Breite auf 100% setzt, dann ändert sich beim Öffnen des Popups die gesamte Breite der Seite, da die Scrollbalken entfernt werden. Die fix positionierten Elemente sind vom margin-right im html-Element wenig beeidruckt.

Lösung: Magnific Popup bietet viele Callbacks, die man nutzen kann, um die fixe Positionierung der Elemente zu korrigieren.

var magnificpopupSettings = {
        callbacks: {
            beforeOpen: function () {
                if($('nav.top-bar').css('position') == 'fixed') {
                    $('nav.top-bar').css('margin-right', '17px');
                    $('#top-link').css('margin-right', '17px');
                }
            },
            afterClose: function() {
                if($('nav.top-bar').css('position') == 'fixed') {
                    $('nav.top-bar').css('margin-right', '');
                    $('#top-link').css('margin-right', '');
                }
            }
        }
    };
};

Diskussion zum Thema: Jumping background

Geschrieben in javascript | Kommentare deaktiviert für Magnific Popup: springen von fix positioniertem Content