Beiträge von natalia

Redirect von URLs mit .html auf /

Vor TYPO3 9 und dem Routing im Core war RealURL sehr gnädig, was URLs anging. So konnte eine Seite mit .html Suffix und auch mit / aufgerufen werden (mein Empfinden). In TYPO3 10 funktioniert diese Verhalten nur in eine Richtung. Konfiguriert man eine .html Endung über den PageSuffix, ist die Seite weiterhin mit Trailing Slash aufrufbar. Es ist eigentltich gut, dass entweder das eine oder das andere geht. Wenn sie jemand die URL mit .html Endung gebookmarked hat und dann statt der Seite 404 bekommt, ist es ärgerlich.

Dank dieses sehr guten Beitrags auf stackoverflow hier mein Redirect-Schnipsel für die htaccess-Datei.

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)\.html$ /$1/ [L,R=302]

Als Tipp stand in dem Beitrag, dass man während des Testings den Fehlercode 302 verwenden sollte (found) und erst wenn es final ist, dann 301, weil dieser vom Browser gecached wird.

TYPO3 Meldung bei gesperrten Records im Backend

Ich arbeite ja fast immer in der englischen Version, daher ist mir dieser Fehler nicht aufgefallen. Wenn ein anderer Benutzer einen Datensatz (z.B. eine Seite) zum Bearbeiten geöffnet hat, dann wird im TYPO3 Seitenbaum eine Meldung angezeigt. Im Englischen lautet die Meldung wie folgt:
“The user ‘%s’ began to edit this record %s ago.” Dann wird der Benutzertyp und das Datum eingesetzt, passt soweit.

Ist das Backend auf deutsch eingestellt, sieht diese Meldung etwas komisch aus.

Die Anführungsstriche werden mit HTML-Entities dargestellt.

Die Suche im Netz gab diesen wertvollen Beitrag aus der TYPO3-Doku aus: https://docs.typo3.org/m/typo3/reference-coreapi/9.5/en-us/ApiOverview/Internationalization/ManagingTranslations.html

Die Übersetzungen fürs Backend können also in einer eigenen Extension überschrieben werden.

Das in die ext_localconf.php einer eigenen Extension:

$GLOBALS['TYPO3_CONF_VARS']['SYS']['locallangXMLOverride']['de']['EXT:core/Resources/Private/Language/locallang_core.xlf'][] = 'EXT:my_extension/Resources/Private/Language/de.locallang-core.xlf';

Dann unter dem angegebenen Pfad die xlf-Datei anlegen und folgendes rein:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<xliff version="1.0">
    <file source-language="en" datatype="plaintext" date="2013-03-09T18:44:59Z" product-name="examples">
        <header/>
        <body>
            <trans-unit id="labels.lockedRecord" resname="labels.lockedRecord" approved="yes">
                <source>The user '%s' began to edit this record %s ago.</source>
                <target state="translated">Der Benutzer '%s' hat mit der Bearbeitung dieses Datensatzes vor %s begonnen.</target>
            </trans-unit>
            <trans-unit id="labels.lockedRecord_content" resname="labels.lockedRecord_content" approved="yes">
                <source>The user '%s' began to edit content on this page %s ago.</source>
                <target state="translated">Der Benutzer '%s' hat mit der Bearbeitung dieser Seite vor %s begonnen.</target>
            </trans-unit>
            <trans-unit id="labels.lockedRecordUser" resname="labels.lockedRecordUser" approved="yes">
                <source>The %s '%s' began to edit this record %s ago.</source>
                <target state="translated">Der %s '%s' hat mit der Bearbeitung dieses Datensatzes vor %s begonnen.</target>
            </trans-unit>
            <trans-unit id="labels.lockedRecordUser_content" resname="labels.lockedRecordUser_content" approved="yes">
                <source>The %s '%s' began to edit content on this page %s ago.</source>
                <target state="translated">Der %s '%s' hat mit der Bearbeitung dieser Seite vor %s begonnen.</target>
            </trans-unit>
        </body>
    </file>
</xliff>

Klappt! Hab ich schon erwähnt, dass ich TYPO3 toll finde 😀

Sport

Dieser Blogbeitrag beschäftigt sich mit meiner zweiten Leidenschaft neben TYPO3 – dem Sport. Ich beschreibe meine Sport-Karriere und wie sich das ganze im letzten Jahr pandemie-geschuldet verändert hat.

(mehr …)

TYPO3 – Domain Property als Boolean validieren

Ich habe lange gesucht und kein Beispiel gefunden, daher hoffe ich, dass ich damit jemandem die Sucherei und das Quellcode-Lesen ersparen kann.

Möchte man eine Property als Boolean validieren, dann lautet die Annotation wie folgt:

use TYPO3\CMS\Extbase\Annotation as Extbase;
 
/**
 * @var boolean
 * @Extbase\Validate(validator="Boolean", options={"is": "1"})
 * @Extbase\ORM\Transient
 */
protected $privacy;

Die Begründung ist, dass für die Abwärtskompabitilität nicht per Default validiert werden kann. Denn alle Properties vom Typ Boolean werden damit validiert.

Ddev, neues Modem (FritzBox) und DNS_PROBE_FINISHED_NXDOMAIN

Ich habe mir ein neues Modem (konkret eine FritzBox) zugelegt. Auf den ersten Blick schien alles so zu funktionieren wie vorher. Heute ist Montag, Zeit zu arbeiten: ddev gestartet und festgestellt, dass meine .ddev.site Domain mit der Meldung DNS_PROBE_FINISHED_NXDOMAIN nicht angezeigt wird. Beim ddev start gab es keine Fehlermeldungen, die Datenbank ist da und auf die Seite kann ich mit einer IP-Adresse zugreifen. Damit bleibt nur noch die Vermutung, dass die DNS-Auflösung die Ursache ist.

Nachdem ich die ddev-Troubleshooting-Seite und ein paar Foren durchsucht habe, stieß ich auf den entscheidenden Hinweis.

Die FritzBox hat ein Feature, das DNS-Antworten unterdrückt, die aufs eigene Netzwerk verweisen (DNS-Rebind-Schutz). Man muss die ddev.site dort unter Ausnahmen eintragen, damit die DNS-Auflösung funktioniert. Diese Option findet man unter Heimnetz > Netzwerk > Netzwerkeinstellungen.

FritzBox Einstellungen

Unable to load dynamic library ‘memcached.so’

Ich habe lokal unter Windows 10 ddev mit Ubuntu 20.04. aufgesetzt. Nach dem Login per SSH (ddev ssh) und dem Ausführen fast jeden Befehls (composer dump-autoload) kam die folgende Warnung:

PHP Warning: PHP Startup: Unable to load dynamic library '/usr/lib/php/20190902/redis.so' (tried: /usr/lib/php/20190902/redis.so (/usr/lib/php/20190902/redis.so: undefined symbol: igbinary_serialize), /usr/lib/php/20190902//usr/lib/php/20190902/redis.so.so (/usr/lib/php/20190902//usr/lib/php/20190902/redis.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
PHP Warning: PHP Startup: Unable to load dynamic library 'memcached.so' (tried: /usr/lib/php/20190902/memcached.so (/usr/lib/php/20190902/memcached.so: undefined symbol: igbinary_serialize), /usr/lib/php/20190902/memcached.so.so (/usr/lib/php/20190902/memcached.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0

Erstmal hatte ich mich auf den “Unable to load” block fokussiert. Sind die Dateien da? – ja. Ist der Pfad richtig? – ja. Ist das Modul installiert? – ja. Es gab viele “Lösungsvorschläge” im Netz. Und es ist so unglaublich frustrierend, wenn das Problem als gelöst gilt (“That solved it, thanks”) und es bei mir immer noch da ist. Ich habe sogar den ddev Installationsvorgang fast komplett neu gemacht. Immer noch die gleiche blöde Warnung, die mir die Konsole zumüllt.

Dann habe ich eher auf den zweiten Teil der Meldung geschaut und danach gesucht: “undefined symbol: igbinary_serialize”.

Lösung: Die aktuellere Version von redis (wohl auch memcached) setzt voraus, dass die Bibiothek igbinary bereits geladen ist. An einer Stelle stand, dass es theoretisch der Fall sein müsste, denn die Bibliotheken werden alphabetisch geladen und i kommt wohl vor r. Wenn man im Ordner .ddev/php eine Konfigurationsdatei ablegt, dann wird diese beim Starten des Containers geladen. Um das igbinary vor den anderen Bibliotheken zu laden, reicht es aus, wenn man eine leere Datei namens 10-igbinary.ini in diesem Ordner ablegt. Da alle anderen Biblitheken eine 20 als Priorität haben, wird die Datei mit 10- früher berücksichtigt.

Vielleicht ist ja jemand irgendwann genauso verzweifelt wie ich und diese “Lösung” hilft ihm/ihr. Lasst es mich wissen…

TYPO3 auf ohne Composer umstellen

Ich persönlich arbeite mittlerweile sehr gerne mit Composer. Man kann schnell Extensions installieren/aktualisieren und TYPO3 aktualisieren. Bei jeden neuen Projekt arbeite ich mit Composer, außer es ist aus irgendwelchen Gründen doch nicht möglich – fehlende SSH Zugänge oder ähnliches. Manchmal kommt es vor, dass man eine mit Composer installierte TYPO3-Instanz von Composer wieder trennen muss.

Es ist eigentlich einfach – in der Datei vendor/typo3/cms-composer-installers/autoload-include.php nur den folgenden Abschnitt entfernen:

// TYPO3 is installed via composer. Flag this with a constant.
if (!defined('TYPO3_COMPOSER_MODE')) {
    define('TYPO3_COMPOSER_MODE', TRUE);
}

In meinem Fall hatte ich auch den crawler installiert. Nachdem ich den Abschnitt entfernt und alle Caches gelöscht hatte, wurde mein Backend nicht geladen mit der Fehlermeldung:

Warning: Uncaught TYPO3\CMS\Core\Error\Exception: PHP Warning: require([...]/public/typo3conf/ext/ crawler//Resources/Private/Php/ Libraries/vendor/autoload.php): failed to open stream: No such file or directory

In der ext_localconf.php der Extension crawler findet man folgendes:

if (!\TYPO3\CMS\Core\Core\Environment::isComposerMode()) {
require \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('crawler') . '/Resources/Private/Php/Libraries/vendor/autoload.php';
}

Der angegebene Pfad existiert aber nicht. Ein Ticket beim Crawler-Issue-Tracker und ein paar Stunden später ist die Lösung eigentlich banal: Extension crawler aus dem TER neu installieren. Sobald man das tut, dann werden die erforderlichen Pakete (die bei der Installation per Composer in vendor installiert werden) unter dem entsprechenden Pfad abgelegt.

Gedankensalat zu TYPO3 Umsetzungen

Ich arbeiten ja nun bereits seit mehreren Jahren (seit 2007?) mit TYPO3 und dachte, dass ich schon fast alles gesehen hätte an Implementierungen und Ansätzen. Immer wieder werde ich überrascht von kreativen Ansätzen mancher Entwickler und Agenturen und stelle fest, dass ich auf solche Ansätze nicht gekommen wäre. Es gibt in der Programmierung nicht die eine “Wahrheit” (auch wenn manche gerne so tun als ob), sondern mehrere Lösungsmöglichkeiten für ein Problem. Manchmal denke ich darüber nach, ob in einem bestimmten Fall mein gewählter Ansatz der richtige ist. In der Retrospektive stelle ich dann bei fast allen Projekten fest, dass ich es aus heutiger Sicht anders gemacht hätte – und das ist auch gut so, das bedeutet, dass man sich persönlich weiterentwickelt hat. Als Freelancerin habe ich Einblick in viele Projekte, an denen vorher andere Agenturen oder Freelancer gearbeitet haben und manchmal würde ich (gelegentlich nach stundenlangem Fluchen) diese gerne fragen, warum sie dieses oder jenes so umgesetzt haben und nicht anders.

Das sind grob die Szenarien, die mir bisher begegnet sind

1) Nicht konfigurierbare Zielseiten/Texte/E-Mail-Adressen etc.
Wenn man schon dabei ist, einen Text oder eine E-Mail-Adresse einzugeben, sollte man sie nicht per TypoScript/Flexform/Datenbank-Feld konfigurierbar machen? Natürlich ist es schön, noch etwas Geld zu verdienen, wenn der Kunde eine Textkorrektur haben möchte oder die E-Mail-Adresse sich geändert hat. Habe ich dazu Lust? Nein.

2) Keine Verwendung von TYPO3 (Core) Konfigurationsmöglichkeiten
Man kann sich in TYPO3 mittels TSconfig doch zu Tode konfigurieren – was ich gerne nutze. Wenn das nicht hilft, gibt es doch Hooks und man kann auch direkt in ext_localconf und ext_tables konfigurerieren. Mit Backend Layouts und Content Defender kann man mittlerweile ziemlich genau einschränken, welche Inhaltselemente in welchen Spalten erlaubt sind. Letztens bin ich über eine Extension gestolpert, die den Content Element Browser und die Auswahl der Inhaltselemente (mit Hooks, höhö) kompett umgeschrieben hat. Welche Inhaltselemente erlaubt sind, steht in einer JSON-Datei. Wenn man diese Schema immer wieder verwendet, ist es natürlich gut. Wenn man als Agentur Entwickler ausbildet, dann zeigt man denen einmal, wie es funktioniert und schon können sie Inhaltselemente zusammenkloppen. Ich bezweifle jedoch, dass sie verstehen, was da im Hintergrund passiert. Jemand, der dieses Schema nicht kennt und das Projekt anschließend übernimmt, muss sich in diese Implementierung reindenken und entweder weiter damit arbeiten oder es so umschreiben, dass es näher an den Vorgaben ist.

3) Inhaltselemente nur für eine bestimmte Verwendung implementieren
Das bezieht sich vor allem aufs Frontend. Immer wieder erlebe ich, dass die Inhaltselemente nur genauso wie bisher eingepflegt gut aussehen und anderswo zwar positioniert werden können, aber dann das Layout zerschießen. Ich sehe TYPO3 mehr wie Duplo: verschiedene Bausteine und ich kann sie beliebig einsetzen. Ein Akkordion z.B. kann im Header, Inhalt und Footer eingesetzt werden und funktioniert dort immer wie erwartet. Wenn das nicht möglich sein soll, dann konfiguriere ich ihn in der Spalte weg (siehe Punkt 2) oder ich sorge per CSS dafür, dass er an allen drei Locations gut aussieht. Bei den meisten Implementierung ist es eher Mikado: So steht es gerade, aber bloß nicht anfassen.
Auch prüfe ich, ob ein Element zweimal auf der Seite einsetzen werden kann! Letztens gerade wieder eine Implementierung gesehen, bei der einem solchen Element ein feste HTML-id zugeteilt wurde und dann mit JavaScript irgendwas damit gemacht wurde. Blöd nur, wenn der Kunde das Element zweimal auf einer Seite einsetzt.

Das reicht fürs erste an Gruselgeschichten. Es hat auch Vorteile, dass mir immer wieder unterschiedliche Projekte begegnen. Zum einen werden meine grauen Zellen stimuliert – ich muss mich in andere Ansätze und Lösungen reindenken und arbeite nicht immer nach dem gleichen Schema. Zum anderen hinterfrage und optimiere ich immer wieder meine eigenen Ansätze und kann so das (zu dem jeweiligen Zeitpunkt) beste Ergebnis abliefern. Und diesen Aspekt mag ich besonders an meinem Freelancer-Dasein…

TYPO3 9 – Twitter-/Facebook Meta-Angaben mit Fallback auf Standardfelder

In TYPO3 9 gibt nach der Installation der Core-Extenson seo in den Seiteneigenschaften Felder, um Meta-Informationen für Facebook und Twitter zu hinterlegen. Diese Informationen werden im Quellcode ausgegeben, wenn man die Felder entsprechend ausgefüllt hat. Nun möchte man nicht explizit immer diese Informationen angeben müssen, vor allem weil der Facebook-/Twitter-Beschreibungstext wahrscheinlich eh identisch ist mit dem Beschreibungstext der Seite. Und die Bilder können automatisch aus dem Feld media generiert werden, wenn es denn befüllt ist.

Mit dem folgenden TypoScript lassen sich Beschreibung und Seitentitel in die Facebook-/Twitter-Meta-Angaben der Seite schreiben. Sobald die “richtigen” Felder ausgefüllt werden, dann werden die Angaben daraus ausgegeben.

page.meta {
    og:title.cObject = TEXT
    og:title.cObject {
        field = title
    }
 
    og:description.cObject = TEXT
    og:description.cObject {
        field = description
        # data = levelfield:-1 , description, slide
    }
 
    og:image.cObject = IMG_RESOURCE
    og:image.cObject.file {
        import.data = levelmedia:-1, slide
        treatIdAsReference = 1
        import.listNum = 0
        maxW = 1200
        cropVariant = default
    }
    og:image.cObject.stdWrap.dataWrap = {getIndpEnv:TYPO3_REQUEST_HOST}/
}

Damit es mit dem Levelfield Slide funktioniert, müssen die Felder in addRootLineFields aufgenommen werden. Das kann entweder “global” in der LocalConfiguration.php gemacht werden oder die Extension fügt diese der vorhandenen Liste hinzu.

In meinem Beispiel habe ich weiterehin Crop Varianten für das Media-Feld der Seite definiert und greife auf diese zu. Wenn das nicht gewünscht ist, dann die Zeile mit cropVariants = default einfach weglassen.

Slick Carousel Play/Pause Indikator

Ich bin seit ein paar Jahren großer Fan von Slick. Die Slideshow ist gut konfigurierbar und kann vielseitig eingesetzt werden.

Für mein aktuelles Projekt brauchte ich eine Möglichkeit die Slideshow zu stoppen und einen Indikator, ob die Slideshow im Autoplay läuft.

Ich habe also einen einfachen Button erstellt, ihn dann mit CSS so gestaltet, dass er von Play zu Pause transformiert und dieses JavaScript eingesetzt:

$('#stage-slideshow-playpause').on('click', function() {
    if($(this).hasClass('play')) {
        $(this).removeClass('play').addClass('pause');
        $('#stage-slideshow').slick('slickPause');
    } else {
        $(this).removeClass('pause').addClass('play');
        $('#stage-slideshow').slick('slickPlay');
    }
});
$('#stage-slideshow .slide').on('mouseenter.slick', function () {
    $('#stage-slideshow-playpause').removeClass('play').addClass('pause');
});
$('#stage-slideshow .slide').on('mouseleave.slick', function () {
    $('#stage-slideshow-playpause').removeClass('pause').addClass('play');
});

Im ersten Block kann man die Slideshow mit dem Button steuern, in den nächsten Abschnitten “reagiert” der Button auf externe Veränderung der Slideshow. Voraussetzung ist, dass die Option pauseOnHover aktiviert ist. Es gibt noch die Option pauseOnDotsHover, falls diese Option an ist, müsste man den Code entsprechend erweitern.