Die „text-underline-offset“ und viele weitere weniger bekannte CSS-Eigenschaften für Links

Die Arbeit für eine große Agentur hat viele Vorteile. Einer davon ist es, dass man mit vielen Menschen zusammenarbeitet, die sehr viel mehr über sehr viele Dinge wissen. Ich lese daher oft Commits von anderen und schaue mir an, was sie so machen. In einem Commit habe ich eine CSS-Eigenschaft entdeckt, sie ich noch nie zuvor gesehen hatte: text-underline-offset 🤯

Links gestalten

In den frühen Tagen des World Wide Web waren alle Links unterstrichen. Mit dem Aufkommen von CSS und modernen Design wollten Leute dann aber andere Styles für Links haben. Die Farbe und Position der text-decoration: underline wurde aber von der Schrift und ihrer Farbe vorgegeben und daher wurde sie oft durch einen border-bottom ersetzt. Das hatte aber mehrere negative Folgen, nicht zuletzt in vielen Fällen eine Verschlechterung der Barrierefreiheit von Links. Mit modernem CSS gibt es nun aber viele verschiedene CSS-Eigenschaften, um Links zu stylen … und von vielen davon habe ich noch nie gehört:

Auf den verlinkten MDN Web Docs Seiten könnt ihr viele der oben erwähnten CSS-Eigenschaften direkt selbst ausprobieren. Ich könnte euch sicher auch viele Bespiele nennen, wie man diese einsetzen kann und wie es dann aussehen würde, aber ich möchte spoilern und euch den Spaß nehmen, es selbst herauszufinden 😉

Fazit

Bei der Entwicklung von Websites gibt es eine unumstößliche Wahrheit: Du kannst nicht alles kennen. Daher kann ich es nur allen empfehlen, den Code von anderen zu lesen und sich von deren Arbeit inspirieren zu lassen. Diese CSS-Eigenschaften mögen für eine von euch bereits bekannt sein, für mich waren die überragend!

Erstellen eines dynamischen iCalendar mit Blog-Beiträgen

Eine kleine lokale Website verwendet ein Terminbuchungs-Plugin. Diese Buchungen wurden anschließend manuell in einen Online-Kalender übertragen, damit das Team einen Überblick über die Buchungen hatte. Leider unterstützte das Plugin keine Funktion, mit der die Buchungen dynamisch in einer Kalender-App angezeigt werden konnten. Daher wurde ich gefragt, ob ich hier helfen kann. Um das Gezeigte aber für euch aber etwas nützlicher zu machen, werde ich nicht zeigen, die ich das für dieses spezielle Terminbuchungs-Plugin umgesetzt habe, sondern wie man den gleichen Ansatz verwenden kann, um alle eure veröffentlichten und geplanten Beiträge anzuzeigen.

Installieren der Abhängigkeiten

Wir werden eine dynamische .ical Datei erstellen. Das ist eine Textdatei und wir könnten den „Code“ einfach selbst erzeugen, aber wir verwenden eine kleine Bibliothek, die uns dabei helfen wird. Für das Projekt habe ich die spatie/icalendar-generator Library verwendet, welche alles hatte, was ich brauchte (und noch vieles mehr). Wir installieren sie einfach mit Composer in unseren Plugin-Ordner:

composer require spatie/icalendar-generator

Anschließend müssen wir die notwendigen Dateien laden. Der einfachste Weg, das mit Paketen zu tun, die mit Composer installiert wurden, ist die Verwendung des Composer-Autoloaders. Hierzu fügen wir diesen der PHP-Datei des Plugins hinzu:

require_once 'vendor/autoload.php';

Erstellung eines Kalenders

Jetzt können wir damit loslegen, einen dynamischen Kalender zu erstellen. Ich werde hier sehr einfache Codebeispiele verwenden, in der Dokumentation des Pakets findet ihr aber auch einige komplexere. Erstellen wir also erst einmal das $calender Objekt:

$calendar = Calendar::create( 'example.com' );

Der Parameter der create() Funktion wird für den Titel verwendet, er kann aber auch leer gelassen werden. Nachdem wir nun unser $calender Objekt haben, würden wir uns die Daten für die Events holen. Hier führen wir lediglich eine Query aus, die uns die letzten veröffentlichten und geplanten Blog-Beiträge holt:

// Get all blog posts.
$query_args = [
	'post_type'      => 'post',
	'post_status'    => [
		'publish',
		'future',
	],
	'posts_per_page' => - 1,
];

$posts = get_posts( $query_args );

Nachdem wir alle Beiträge geladen haben, können wir für jeden davon ein individuelles Event erstellen:

// Create an event per blog post.
foreach ( $posts as $post ) {
	$start_date = new DateTime( $post->post_date_gmt, new DateTimeZone( 'UTC' ) );
	$end_date   = ( clone $start_date )->add( new DateInterval( 'PT15M' ) );

	$event = Event::create();
	$event->name( $post->post_title );
	$event->startsAt( $start_date );
	$event->endsAt( $end_date );
	$event->uniqueIdentifier( $post->ID );

	$calendar->event( $event );
}

Wir verwenden hier GMT/UTC Zeiten, um es dem Kalender zu ermöglichen, sich an die lokale Zeitzone anzupassen. Für das „Enddatum“ addieren wir 15 Minuten dazu. Durch die Verwendung der Post-ID als „unique identifier“ kann eine Kalender-Applikation einfacher einzelne Events aktualisieren/synchronisieren. Zum Schluss fügen wir dann noch das Event zum zuvor erstellten $calendar Objekt hinzu.

Im letzten Schritt müssen wir dann noch sie Ausgabe senden. Das wird mit folgenden Zeilen erreicht:

// Print the calendar output.
header( 'Content-Type: text/calendar; charset=utf-8' );
echo $calendar->get();
exit;

Importieren des iCalendar in eine Kalender-Anwendung

Den fertigen dynamischen Kalender wollen wir jetzt vermutlich in eine Kalender-App importieren (und synchronisieren). Dazu muss er über eine URL aufrufbar sein, die wir in der Anwendung eintragen müssen. Ich habe mich dazu entschieden, hierfür einen eigenen REST-Endpoint zu erstellen. Den bisherigen Code habe ich also in eine Callback-Funktion verschoben und dann den Endpoint wie folgt registriert:

function blog_posts_calendar_register_rest_route() {
	register_rest_route(
		'blog-posts-calendar/v1',
		'/ical.ics',
		[
			'methods'             => 'GET',
			'callback'            => 'blog_posts_calendar_generate_ical',
			'permission_callback' => '__return_true',
		]
	);
}
add_action( 'rest_api_init', 'blog_posts_calendar_register_rest_route' );

Der Kalender kann dann über folgende URL aufgerufen werden: https://example.com/wp-json/blog-posts-calendar/v1/ical.ics

Der permission_callback würde es hier allen erlauben, den Kalender zu abonnieren. Wenn ihr etwas restriktiver sein wollt, könnt ihr hier eure eigene Callback-Funktion implementieren.

Fazit

Selbst wenn ein Plugin eine Funktion mal nicht anbieten, kann man meistens etwas eigenen Code schreiben, um es verfügbar zu machen. Das ist oft auch einfacher als das ganze Plugin durch ein anderes zu ersetzen, was dann vielleicht eine andere Funktion nicht mehr hat. Im Falle eines Terminbuchungs-Plugins kann es vermutlich besonders schwer sein, dies durch ein anderes zu ersetzen, da man auch alle Daten zum andern Plugin migrieren muss.

Wenn ihr den Code mal selbst mit eurer Website testen wollt, dann findet ihr in als Plugin auf GitHub.

Verwendung von unterschiedlichen Git-Einstellungen für persönliche und berufliche Projekte

Wenn ihr für eine Agentur oder selbstständig tätig seid, gleichzeitig aber auch persönliche Projekte habt, dann möchtet ihr vielleicht unterschiedliche Git-Konfigurationen für diese beiden Arten von Projekten verwenden. Wenn ihr euren Rechner aufsetzt, um an Projekten zu arbeiten, dann konfiguriert ihr einige grundlegende Tools wie Git und GitHub. Diese Einstellungen beinhalten in der Regel die Git-Autor E-Mail-Adresse und eventuell auch einen zugehörigen SSH/GPG-Key für die Signierung von Commits. Man kann normalerweise nur eine globale E-Mail-Adresse definieren, ihr verwendet hier dann also vermutlich die professionelle E-Mail-Adresse (des Unternehmens). Wenn ihr aber auch an privaten oder Open-Source-Projekten arbeiten wollt, dann bevorzugt ihr hier vielleicht eher eure persönliche GitHub E-Mail-Adresse. Diese Anleitung soll euch dabei helfen, das einzustellen.

Vorbereitung

In dieser Anleitung gehen wir davon aus, dass ihr PhpStorm verwendet und all eure Projekte, an denen ihr arbeitet, im Ordner ~/PhpstormProjects gespeichert sind. Derselbe Ansatz funktioniert aber natürlich auch mit einer anderen IDE und einer anderen Ordnerstruktur.

Damit ihr zwei verschiedene E-Mail-Adressen mit GitHub verwenden könnt, müsst ihr diese erst einmal verifizieren. Anschließend könnt ihr bei jeder Aktion, wie etwa dem Merge eines PR auf github.com, auswählen, welche ihr verwenden möchtet. Die Verifizierung ist weiterhin notwendig, damit ihr sie für die Signierung von Commits verwenden könnt.

Aufteilung euer Konfiguration und bedingtes Laden der Dateien

Normalerweise speichert ihr alle Git-Konfigurationen in der Datei ~/.gitconfig. Diese Datei könnte wie folgt aussehen:

[user]
	name = Jo Doe
	email = j.doe@company.com
	signingkey = ~/.ssh/id_ed25519.pub

In jedem Projekt, in dem ihr nun einen Commit macht, würde die company.com E-Mail-Adresse verwendet und der Commit mit dieser signiert. Wenn ihr eine andere E-Mail-Adresse verwenden wollt, müsstet ihr diese manuell bei jedem Commit angeben:

git commit -m"message" --author="Jo Doe <1234567+jo.doe@users.noreply.github.com>" --gpg-sign=~/.ssh/id_ed25519.pub

Das ist natürlich nicht wirklich praktikabel und ihr vergesst diese zusätzlichen Parameter vielleicht manchmal. Alternativ könnt ihr auch die Autor-Einstellungen in der projektspezifischen Konfigurationsdatei angeben. Aber das müsstet ihr in jedem einzelnen geklonten Projekt tun.

Es ist zwar ebenfalls technisch möglich, die Autor-Daten nachträglich zu ändern, aber das ist alles andere als leicht und erfordert auch einen interaktiven Rebase, der die Historie überschreibt und daher nicht für bereits gepushte Commits verwendet werden darf. Wie können wir es also sonst erreichen?

Bedingtes Laden einer Konfigurationsdatei

Innerhalb einer Git-Konfigurationsdatei kann man mit includeIf eine Datei aufgrund einer Bedingung laden. Der einfachste Weg ist es hierbei, dies abhängig vom Projektordner zu tun. Wir speichern hierzu einfach alle persönlichen und Open-Source-Projekte direkt im ~/PhpstormProjects Ordner und alle für das Unternehmen im Unterordner ~/PhpstormProjects/company. Wir können dann die folgenden Zeilen in unsere globale Git-Konfiguration einfügen:

# file: ~/.gitconfig

[includeIf "gitdir:~/PhpstormProjects/"]
	path = .gitconfig-general

[includeIf "gitdir:~/PhpstormProjects/company/"]
	path = .gitconfig-company

Wir verschieben dann alle spezifischen Eisntellungen in diese beiden Dateien. Das werden in der Regel mindestens die gesamten [user] Einstellungen sein, so wie eventuell noch ein paar mehr. Die allgemeine Datei könnte dann wie folgt aussehen:

# file: ~/.gitconfig-general

[user]
	name = Jo Doe
	email = 1234567+jo.doe@users.noreply.github.com
	signingkey = ~/.ssh/id_ed25519.pub

In diesem Codeschnippsel habe ich die GitHub „noreply“ E-Mail-Adresse verwendet, die ihr anstelle eurer persönlichen E-Mail-Adresse einstellen könnt, damit diese private belibt und nicht für den Versand von Spam missbraucht wird.

Für den beruflichen company Unterordner sieht die separate Konfigurationsdatei dann etwa so aus:

# file: ~/.gitconfig-company

[user]
	name = Jo Doe
	email = j.doe@company.com
	signingkey = ~/.ssh/id_ed25519_company.pub

Diese beiden Dateien werden in eurem Homeverzeichnis neben der globalen .giconfig Datei gespeichert. Wenn ihr weiter Einstellungen überschreiben möchtet, fügt sie einfach der jeweiligen Datei hinzu.

Auf der Git-Dokumentationsseite zu den includes findet ihr auch andere Bedingungen für das Laden von Dateien, unabhängig vom gitdir. Ihr könnt beispielsweise auch eine Konfigurationsdatei abhängig von der Remote-URL laden. Damit wäre es dann auch möglich ein company Repository in einen anderen Ordner als ~/PhpstormProjects/company/ zu klonen un trotzdem die .gitconfig-company Datei zu laden. Aber es ist vermutlich einfacher zu verstehen, welche Datei geladen wird, wenn man nach dem Ordner geht.

Bonus: Verwaltung von GitHub-Benachrichtigungen für mehrere E-Mail-Adressen

Wenn ihr eine Einladung zu einer GitHub-Organisation erhaltet, dann werden ihr in der Regel automatisch alle Benachrichtigungen von allen Repositories abonniert. Da ihr GitHub vermutlich zuvor nur für private Projekte verwendet habt, erhaltet ihr diese Benachrichtigungen dann auch alle an die private E-Mail-Adresse. Glücklicherweise kann man ein Routing für Benachrichtigungen basierend auf der Organisation einstellen.

Um eine solche Routing-Regel einzustellen, navigiert ihr zu „Settings | Notific ations„. Dort klickt ihr dann auf den „Custom Routing“ Button und dann auf den „Add new route“ Button. Anschließend wählt ihr die Organisation und die E-Mail-Adresse aus und klickt auf „Save“. Wenn ihr Einladungen zu mehreren Organisationen bekommen habt, könnt ihr auch mehrere Regeln anlegen.

Falls ihr gitlab.com für private/berufliche Projekte verwendet, dann könnt ihr pro Gruppe oder Projekt eine E-Mail-Adresse für die Benachrichtigungen auswählen. Diese findet ihr in eurem Profil unter „User Settings | Notifications„.

Fazit

Die gleichzeitige Arbeit an privaten und beruflichen Git-Projekten auf einem Gerät kann die Git-Autor-Daten eurer Commits ziemlich durcheinanderbringen. Aber mit dem bedingten Laden von Konfigurationsdateien kann spezifische Einstellungen für die verschiedenen Arten von Projekten festlegen.

Führe deinen eigenen Code innerhalb einer Action nur einmal aus

WordPress bietet sehr viele Hooks an, mit denen man mit dem Code interagieren kann. Filter ändern dabei den Wert einer übergebenen Variablen und in der Regel möchte man diesen Wert jedes Mal ändern, wenn der Filter angewendet wird. Bei Actions möchte man den Code aber vielleicht nur einmal ausführen, gerade wenn er Seiteneffekt, wie etwa den Versand einer E-Mail auslösen.

Prüfe, ob eine Action bereits ausgeführt wurde

Mit der Funktion did_action() kann man prüfen, wie häufig eine Action bereits aufgerufen wurde. Wenn ihr euren eigenen Code also nur dann ausführen wollte, wenn die Action zum ersten Mal ausgeführt wird, aber kein zweites Mal, dann könnt ihr das folgende tun:

function do_this_only_once() {
	// If the action we are hooking in was called more than once, stop execution of the custom code.
	if ( did_action( 'the_hook_name' ) > 1 ) {
		return;
	}

	// Run your custom code
}
add_action( 'the_hook_name', 'do_this_only_once' );

Wenn die the_hook_name Action ausgeführt wird, dann wird beim Ausführen von did_action( 'the_hook_name' ) der Wert 1 zurückgegeben, da die Action gerade eben ausgeführt wurde. Daher könnt ihr nicht einfach auf einen boolschen Wert testen, sondern ihr müsst prüfen, ob der Wert größer als Eins ist, um die Ausführung eures Codes zu stoppen.

Verwendet eure eigene Action, um mehrfache Ausführungen eures Codes zu verhindern

Manchmal könnt ihr nicht einfach prüfen, ob die Action zum ersten Mal ausgeführt wird, sondern ihr müsst noch weiter Dinge prüfen. In einem solchen Fall könnte man diese alle in eine Bedingung schreiben. Alternativ könnt ihr hier aber auch eine eigene Action nutzen, die ihr dann für das frühe Abbrechen der Funktion nutzen könnt:

function do_this_only_once( $hook_parameter ) {
	// If the custom code has been run already, stop execution of custom code.
	if ( did_action( 'do_this_only_once' ) ) {
		return;
	}

	// A second check on a hook parameter.
	if ( 'something' !== $hook_parameter ) {
		return;
	}

	// Run your custom code

	// Call our custom action, so we can check, if it has been called already.
	do_action( 'do_this_only_once' );
}
add_action( 'the_hook_name', 'do_this_only_once' );

In diesem Beispiel prüfen wir in der ersten Bedingung auf unsere eigene Action. Dann führen wir eine weitere Bedingung aus, die die weitere Ausführung abbrechen könnte. Nur wenn auch diese Bedingung die Funktion nicht verlässt, wird der eigene Code schließlich ausgeführt. Ganz am Ende der Funktion führen wir dann unsere eigene Action aus. Die erste Bedingung in der Funktion stellt beim nächsten Aufruf der Funktion nicht nur sicher, dass wir den eigenen Code nicht erneut ausführen, es verhindert auch, dass die weiteren Bedingungen nicht erneut geprüft werden müssen.

Fazit

Es gibt mehrere Wege, um zu verhindern, dass euer Code für eine Action nur einmal ausgeführt wird. Dabei empfiehlt es sich in der Regel, dass man hierzu die did_action() Funktion verwendet. Und wenn eurer Code Seiteneffekte hat, wie etwas das Versenden einer E-Mail, dann solltet ihr wirklich sicherstellen, dass er nur genau so oft aufgerufen wird, wie unbedingt notwendig.

Alle Composer-Pakete auf einmal auf die neueste Version aktualisieren

Wenn ihr in PHP programmiert, dann stehen die Chancen sehr hoch, dass ihr Composer zur Verwaltung der Abhängigkeiten verwendet. Irgendwann möchtet ihr die sicher einmal aktualisieren. Die Hauptversion, die ihr beim ersten Mal installiert habt, könnt ihr mit einem einzigen Befehl aktualisieren, allerdings nicht auf die nächste Hauptversion. Wenn ihr also viele Abhängigkeiten in einem alten Projekt habt, und dann testen wollt, ob diese auch in der neusten Version (mit einer aktuellen PHP-Version) funktionieren würden, müsst ihr normalerweise jedes Paket einzeln aktualisieren, indem ihr die Abhängigkeit erneut hinzufügt:

composer require wp-cli/mustangostang-spyc
composer require wp-cli/php-cli-tools
...

Jetzt könntet ihr natürlich einfach alle Paketnamen aus der composer.json Datei kopieren, aber bei vielen Abhängigkeiten ist das recht aufwändig und man vergisst schnell mal einige. Daher habe ich nach einem Weg gesucht, alle Pakete in nur einem Befehl zu aktualisieren.

Alle Pakete auch einmal aktualisieren

Für diesen Blogbeitrag nehme ich das wp-cli/wp-cli Paket als Beispiel. Es hat eine ganz Reihe an Abhängigkeiten und Dev-Abhängigkeiten.

Alle Pakete als Liste erhalten

Im ersten Schritt müssen wir einen Befehl finden, der uns alle Composer-Pakete unseres Projekts als Liste liefert. Hierzu können wir den composer show Befehl verwenden:

$ composer show -s
name     : wp-cli/wp-cli
descrip. : WP-CLI framework
keywords : cli, wordpress
versions : * 2.7.x-dev
type     : library
license  : MIT License (MIT) (OSI approved) https://spdx.org/licenses/MIT.html#licenseText
homepage : https://wp-cli.org
source   : []  a5336122dc45533215ece08745aead08af75d781
dist     : []  a5336122dc45533215ece08745aead08af75d781
path     : 
names    : wp-cli/wp-cli

support
issues : https://github.com/wp-cli/wp-cli/issues
source : https://github.com/wp-cli/wp-cli
docs : https://make.wordpress.org/cli/handbook/

autoload
psr-0
WP_CLI\ => php/
classmap
php/class-wp-cli.php, php/class-wp-cli-command.php

requires
php ^5.6 || ^7.0 || ^8.0
ext-curl *
mustache/mustache ^2.14.1
rmccue/requests ^1.8
symfony/finder >2.7
wp-cli/mustangostang-spyc ^0.6.3
wp-cli/php-cli-tools ~0.11.2

requires (dev)
roave/security-advisories dev-latest
wp-cli/db-command ^1.3 || ^2
wp-cli/entity-command ^1.2 || ^2
wp-cli/extension-command ^1.1 || ^2
wp-cli/package-command ^1 || ^2
wp-cli/wp-cli-tests ^3.1.6

suggests
ext-readline Include for a better --prompt implementation
ext-zip Needed to support extraction of ZIP archives when doing downloads or updates

In der Ausgabe gibt es zwei Abschnitte mit „requires“ und „requires (dev)“. Allerdings ist diese Ausgabe nur schwer nach den Namen der Pakete zu parsen. Glücklicherweise können wir die Ausgabe auch als JSON-Objekt erhalten, indem wir einfach das --format Argument anhängen:

$ composer show -s --format=json
{
    "name": "wp-cli/wp-cli",
    "description": "WP-CLI framework",
    "keywords": [
        "cli",
        "wordpress"
    ],
    "type": "library",
    "homepage": "https://wp-cli.org",
    "names": [
        "wp-cli/wp-cli"
    ],
    "versions": [
        "2.7.x-dev"
    ],
    "licenses": [
        {
            "name": "MIT License",
            "osi": "MIT",
            "url": "https://spdx.org/licenses/MIT.html#licenseText"
        }
    ],
    "source": {
        "type": "",
        "url": "",
        "reference": "a5336122dc45533215ece08745aead08af75d781"
    },
    "dist": {
        "type": "",
        "url": "",
        "reference": "a5336122dc45533215ece08745aead08af75d781"
    },
    "suggests": {
        "ext-readline": "Include for a better --prompt implementation",
        "ext-zip": "Needed to support extraction of ZIP archives when doing downloads or updates"
    },
    "support": {
        "issues": "https://github.com/wp-cli/wp-cli/issues",
        "source": "https://github.com/wp-cli/wp-cli",
        "docs": "https://make.wordpress.org/cli/handbook/"
    },
    "autoload": {
        "psr-0": {
            "WP_CLI\\": "php/"
        },
        "classmap": [
            "php/class-wp-cli.php",
            "php/class-wp-cli-command.php"
        ]
    },
    "requires": {
        "php": "^5.6 || ^7.0 || ^8.0",
        "ext-curl": "*",
        "mustache/mustache": "^2.14.1",
        "rmccue/requests": "^1.8",
        "symfony/finder": ">2.7",
        "wp-cli/mustangostang-spyc": "^0.6.3",
        "wp-cli/php-cli-tools": "~0.11.2"
    },
    "devRequires": {
        "roave/security-advisories": "dev-latest",
        "wp-cli/db-command": "^1.3 || ^2",
        "wp-cli/entity-command": "^1.2 || ^2",
        "wp-cli/extension-command": "^1.1 || ^2",
        "wp-cli/package-command": "^1 || ^2",
        "wp-cli/wp-cli-tests": "^3.1.6"
    }
}

Jetzt müssen wir das JSON noch parsen. Auf meinem Linux-System steht mir hierzu der Befehl jq zur Verfügung, mit dem man eine JSON-Datei oder Ausgabe parsen kann. Ich habe auch eine kleine Übersicht an nützlichen Argumenten gefunden, mit dem es mir dann möglich war, an den requires Schlüssel zu gelangen:

$ composer show -s --format=json | jq '.requires'
{
  "php": "^5.6 || ^7.0 || ^8.0",
  "ext-curl": "*",
  "mustache/mustache": "^2.14.1",
  "rmccue/requests": "^1.8",
  "symfony/finder": ">2.7",
  "wp-cli/mustangostang-spyc": "^0.6.3",
  "wp-cli/php-cli-tools": "~0.11.2"
}

Das ist schon mal toll! Aber wir brauchen lediglich die Namen der Pakete. Daher lesen wir im nächsten Schritt nur die Schlüssel des Objekts aus:

$ composer show -s --format=json | jq '.requires | keys'
[
  "ext-curl",
  "mustache/mustache",
  "php",
  "rmccue/requests",
  "symfony/finder",
  "wp-cli/mustangostang-spyc",
  "wp-cli/php-cli-tools"
]

Um diese Liste nun in einem anderen Befehl verwenden zu können, brauchen wir sie in einer Zeile. Hierzu können wir add verwenden:

$ composer show -s --format=json | jq '.requires | keys | add'
"ext-curlmustache/mustachephprmccue/requestssymfony/finderwp-cli/mustangostang-spycwp-cli/php-cli-tools"

Das ist nicht wirklich, was wir wollen, dann nach jeden Paketnamen sollte ein Leerzeichen kommen. Das erreichen wir mit map:

$ composer show -s --format=json | jq '.requires | keys | map(.+" ") | add'
"ext-curl mustache/mustache php rmccue/requests symfony/finder wp-cli/mustangostang-spyc wp-cli/php-cli-tools "

Jetzt haben wir es fast geschafft. Wir müssen nur noch die Anführungsstriche um den String entfernen, was uns mit dem -r Parameter gelingt:

$ composer show -s --format=json | jq '.requires | keys | map(.+" ") | add' -r
ext-curl mustache/mustache php rmccue/requests symfony/finder wp-cli/mustangostang-spyc wp-cli/php-cli-tools

Damit haben wir es also. Jetzt können wir das Ergebnis in einem Subbefehl verwenden und endlich mit einem einzelnen Befehl alle Abhängigkeiten aktualisieren:

$ composer require $(composer show -s --format=json | jq '.requires | keys | map(.+" ") | add' -r)
Using version * for ext-curl
Info from https://repo.packagist.org: #StandWithUkraine
Using version ^2.14 for mustache/mustache
Using version ^7.4 for php
Using version ^2.0 for rmccue/requests
Using version ^5.4 for symfony/finder
Using version ^0.6.3 for wp-cli/mustangostang-spyc
Using version ^0.11.15 for wp-cli/php-cli-tools
./composer.json has been updated
...

Das war’s! Wenn ihr in eurem Projekt auch Dev-Abhängigkeiten habt, dann müsst ihr noch einen zweiten Befehl ausführen, bei dem ihr dann an den composer Befehl noch das --dev Argument anhängt und nach devRequires statt requires filtert:

$ composer require --dev $(composer show -s --format=json | jq '.devRequires | keys | map(.+" ") | add' -r)
Using version dev-latest for roave/security-advisories
Using version ^2.0 for wp-cli/db-command
Using version ^2.2 for wp-cli/entity-command
Using version ^2.1 for wp-cli/extension-command
Using version ^2.2 for wp-cli/package-command
Using version ^3.1 for wp-cli/wp-cli-tests
./composer.json has been updated
...

Zusammenfassung

Ich hoffe, dass ich euch in diesem Beitrag erklären konnte, wie ihr mit Composer und einem weiteren Befehl eine solche Aufgabe in einem einzelnen Befehl erledigen könnt. Das hier sind noch einmal die zwei Befehle, die ihr vermutlich benötigt:

Für Abhängigkeiten:

composer require $(composer show -s --format=json | jq '.requires | keys | map(.+" ") | add' -r)

Für Dev-Abhängigkeiten:

composer require --dev $(composer show -s --format=json | jq '.devRequires | keys | map(.+" ") | add' -r)

Ich mag die Möglichkeiten von Kommandozeilen-Tools wirklich sehr, aber es ist manchmal nicht ganz leicht, solche Einzeiler zu finden. Nachdem ich einige Jahre nach einer Lösung für genau dieses Problem gesucht hatte, habe ich mir dann doch endlich mal die Zeit genommen, um eine Lösung zu finden, und zum Glück war ich erfolgreich dabei.

Arbeiten und Reisen – oder wieso Programmieren im Zug eine Herausforderung sein kann

Heute möchte ich mal eine etwas andere Geschichte teilen. Wenn ich in Deutschland reise, dann nehme ich fast immer den Zug. Falls manche von euch schon einmal Berlin Richtung Westen verlassen haben, dann werdet ihr vermutlich auch wissen, dass die Internetverbindung extrem schlecht wird, sobald man Berlin verlassen hat, egal ob über das WLAN im ICE oder euren mobilen Hostpot. Aber wenn ihr Linux verwendet und auf dem Laptop auch entwickelt, dann seid ihr vielleicht gar nicht in der Lage, das Internet über WLAN zu verwenden.

WIFIonICE

Wenn ihr im ICE unterwegs seid, dann gibt es selbst in der 2. Klasse kostenloses WLAN. Es ist ausreichend stark, um die meisten Dinge zu erledigen. Selbst Streaming und Videotelefonate sind möglich, wenn ihr diese wirklich machen müsst. Aber bitte nicht im Ruhebereich! Für das Streaming würde ich euch aber das iceportal.de empfehlen, wo ihr einige Filme und Serien ansehen könnt, ähnlich wie im Flugzeug.

Das klingt alles nicht schlecht, oder? Normalerweise müsst ihr euch dazu nur zu WIFIonICE oder WIFI@DB verbinden und dann öffnet sich euer Browser und fordert euch dazu auf, die AGB zu akzeptieren. Passiert das nicht, könnt ihr einen Browser und auf LogIn.WIFIonICE.de navigieren, wo ihr das dann tun könnt. Ihr habt das getan, und nichts passiert? Dann können euch vielleicht die FAQ auf der DB Seite weiter helfen. Aber ihr würdet diesen Beitrag vermutlich nicht lesen, wenn das funktioniert hätte, richtig?

OK, lasst mich raten: Ihr verwendet sehr wahrscheinlich einen Linux-Laptop, aber auf jeden Fall setzt ihr bei der lokalen Entwicklung Docker ein. Richtig? Dann willkommen zu meinem etwas anderen Thema für einen Blogbeitrag 😉

Docker-Netzwerke und WIFIonICE

Das Problem ist eines, zu dem ihr leider auf der DB Seite keine Hilfe finden werdet. Es ist auch eines, was ich jetzt schon mehr als einmal hatte und was dazu führte, dass ich die Seite mit den AGB nicht sehen konnte. Es war überhaupt nicht möglich, irgendeine Seite zu öffnen. Nichts passierte im Browser. Der Grund dafür ist der folgende: im ICE wird der IP-Adressbereich 172.17.0.0/16 für das WLAN verwendet. Und jetzt ratet mal, welchen IP-Adressbereich Docker standardmäßig für seine Netzwerke verwendet? Ihr seid ja richtig clever 😉

Lösung des Problems

Um euch wieder mit dem WLAN verbinden zu können, müsst ihr das Netzwerk, das die IP-Adresse 172.17.0.1 verwendet entfernen. Das könnt ihr mit den Betriebssystem-Werkzeugen machen (etwa mit ip link delete) oder aber ihr verwendet den docker network rm Befehl. Es würde das Problem lösen, aber es würden eventuell einige Dinge nicht mehr funktionieren, da Docker dieses Netzwerk ja vermutlich braucht. Es ist auch meisten das primäre Bridge-Netzwerk. Und wenn ihr euch in ein paar Monaten erneut in einen ICE setzt, dann ist die IP-Adresse vielleicht schon wieder von einem neuen Docker-Netzwerk belegt. Daher brauchte ich eine bessere Lösung.

Ändern des IP-Adressbereichs

Nach einiger Recherche konnte ich eine Dokumentation zu einer optionalen Konfigurationsdatei finden, mit der ihr einige Variablen für den Docker-Daemon auf eurem System setzen könnt. Auf dieser Seite gibt es auch ein komplettes Beispiel für eine Linux-Konfiguration. Hiervon braucht ihr nur einen kleinen Teil. Zuerst öffnet (oder erstellt) ihr eine Konfigurationsdatei:

sudo vim /etc/docker/daemon.json

Nun fügt ihr (mindestens) die folgenden Zeilen ein und speichert die Datei:

{
  "default-address-pools": [
    {
      "base": "172.30.0.0/16",
      "size": 24
    },
    {
      "base": "172.31.0.0/16",
      "size": 24
    }
  ]
}

Ich habe hier zwei alternative IP-Adressbereiche definiert. Vermutlich würde sogar einer ausreichen. Nach Änderungen an dieser Datei müsst ihr den Docker-Daemon noch neu startet. Für mich war das mit diesem Befehl möglich (unter Manjaro Linux):

systemctl restart docker

Jetzt solltet ihr in der Lage sein, die Seite mit den AGB zu öffnen und endlich mit einer produktiven Coding-Session im Zug anzufangen … oder aber einen Film oder eine Serie aus der Mediathek des ICE genießen 😁

Fazit

Netzwerkprobleme und Probleme mit (öffentlichen) Hotspots kennen wir wohl alle und wir können viele Geschichten darüber erzählen. Aber ich hätte nie gedacht, dass der Einsatz von Docker zu so einem Fehler führen könnte, der anscheinend der DB nicht bekannt ist. Zumindest findet man hierzu nichts in den FAQ zu diesem Problem und ich bin sicher nicht der einzige, der es schon hatte. Wenn euch dieser Blogbeitrag also helfen konnte und ihr nun endlich online seid, dann könnt ihr doch sicher auch kurz einen Kommentar schreiben, oder nicht?☺️

Hinzufügen eines formularspezifischen Gravity Forms Hook zu deinem Code

Wer meinem Blog regelmäßig liest, wird sicher festgestellt haben, dass ich in vielen Projekten Gravity Forms verwende. Ich mag die große Vielfalt an Funktionen des Plugins (und seiner Erweiterungen) sehr, aber vielleicht noch sehr viel mehr die Möglichkeit, viele Dinge anzupassen. Ich habe auch dabei geholfen eine Erweiterung zu schreiben, mit er es möglich ist, alle Dateien eines Eintrags (oder mehrerer Einträge) auf einmal herunterzuladen. Diese Woche haben wir eine neue Version veröffentlicht, die eine kleine aber wichtige Neuerung enthält: die Möglichkeit, Hooks nur für bestimmte Formular zu verwenden.

Hooks in Gravity Forms verwenden

Wenn man Gravity Forms Funktionalitäten anpassen möchte, dann funktioniert das genau wie im Core oder in Plugins/Themes. Ihr verwendet die Funktionen add_action() oder add_filter() für euren eigenen Code. Und Gravity Forms hat sehr viele Actions und Filter.

Sehen wir uns als Beispiel die gform_pre_submission Action an. Ihr könnt diese verwenden, um einen Eintrag zu verändern, bevor Benachrichtigungen versendet wurden und vor der Speicherung des Eintrags in der Datenbank. Ein Anwendungsfall könnte wie folgt aussehen:

function my_gform_pre_submission( $form ) {
    $_POST['input_1'] = strtolower( $_POST['input_1'] );
}
add_action( 'gform_pre_submission', 'my_gform_pre_submission' );

Dies würde den Wert vom Formularfeld mit der ID 1 (input_1) nehmen und den Textwert in Kleinbuchstaben ändern. Dies würde allerdings für jedes einzelne Formular angewendet werden. Wenn ihr es nur für ein Formular anwenden wollt, dann müsst ihr es für die Action so schreiben, dass sie nur für dieses eine Formular greift. Glücklicherweise ist bei Gravity Forms genau das möglich, denn ihr könnt Hook-Namen dynamisch verwenden. Ersetzt hierzu den add_action Aufruf wie folgt:

add_action( 'gform_pre_submission_5', 'my_gform_pre_submission' );

Dieser Action-Name hat einen Suffix _5 und wird daher nur für das Formular mit der ID 5 aufgerufen. Ich hatte das bereits im Blogbeitrag Dynamische Formular-Hooks für GravityForms Anfang des Jahres erklärt und dabei auch beschrieben, wie man mit diesen statischen Werten für die IDs der Formulare in den Hook-Namen umgehen kann.

Nachdem wir also in dieser kleinen Einführung noch einmal die Grundlagen wiederholt habe, können wir in das eigentliche Thema einsteigen und uns ansehen, wie ihr Hooks nur für bestimmte Formulare in eurem eigenen Code verwendet.

Hooks in eurem Code anbieten

Wenn ihr eine Veränderung von Teilen eures Codes erlauben möchtet, dann müsst ihr dazu eine diese beiden Funktionen verwenden:

  1. apply_filters()
  2. do_action()

Ihr könnt euch sicherlich schon denken, welche Funktion hier jeweils für welche Art von Hook verwendet wird. Der Unterschied besteht darin, dass ein Filter einen Wert zurückgibt, eine Action aber nicht. Wenn ihr also einen Filter verwenden möchtet, dann sieht es in etwa wie folgt aus:

$value = apply_filters( 'filter_name', $value, $arg1, $arg2 );

Ihr definiert also einen Namen für den Filter und übergebt die Variable, die verändert werden soll. Optional könnt ihr weitere Argumente übergeben, mit deren Hilfe ihr dann innerhalb der Callback-Funktion entschieden könnt, wie ihr den Wert verändert.

Für eine Action sieht es ziemlich ähnlich aus. Ihr speichert aber keinen Rückgabewert, da die Funktion auch nichts zurückgibt:

do_action( 'action_name', $arg1, $arg2 );

Und weil die Funktion keinen Wert verändert, müsst ihr auch nicht zwingend eine Variable übergeben, es sind also alle Argumente optional. Viele Actions bekommen gar keinen Wert übergeben, wie etwa die wp_head Action.

Den Hook formularspezifischen machen

Nachdem wir nun also gelernt haben, wie man Hooks zum eigenen Code hinzufügt, sehen wir uns jetzt an, wie man diese nur für bestimmte Formular ausführen lässt. In der offiziellen Dokumentation von Gravity Forms findet sich nur eine Seite zu Actions, aber leider ohne viel Erklärung. Die Veränderungen, die ihr machen müsst, sind aber für Actions und Filter identisch. Alles, was ihr tun müsst, ist die Funktionen mit einem gf_ Präfix zu versehen und als ersten Parameter ein Array mit dem Hook-Namen und der Formular-ID zu übergeben. Für einen Filter sieht es dann wie folgt aus:

$value = gf_apply_filters( array( 'filter_name' $form_id ), $value, $arg1, $arg2 );

In diesem Beispiel gehen wir davon aus, dass die Formular-ID in der Variablen $form_id gespeichert ist, sie könnte aber auch in $form['id'], $entry['form_id'] oder etwas Ähnlichem enthalten sein. Ihr müsst also nur sicherstellen, dass ihr sie an die Funktion übergebt. Für eine Action sieht der Funktionsaufruf dann wie folgt aus:

gf_do_action( array( 'action_name' $form_id ), $arg1, $arg2 )

Das war auch schon alles, was ihr tun müsst!

Fazit

Wenn ihr eine Erweiterung, ein Plugin oder eigenen Code für Gravity Forms schreibt, ist es immer gut, wenn ihr euch überlegt, an welchen Stellen jemand eventuell euren Code anpassen möchte. Hierfür dann Hooks zur Verfügung zu stellen, kann euren Code sehr viel besser nutzbar machen. Und wenn ihr diese Hooks hinzufügt, dann verwendet dabei immer die Gravity Forms Funktionen, damit sie spezifisch für individuelle Formulare verwendet werden können.

Optionen zu einem Block hinzufügen

Nachdem wir uns im letzten Beitrag angesehen haben, wie wir modernes JavaScript verwenden können, um einen Block zu schreiben, möchte ich heute zeigen, wieso man das machen möchte, indem wir dem Block Optionen hinzufügen. Ich werde euch dabei nur die Änderungen zeigen, die ihr vornehmen müsst.

Erstellen einer „edit“ Komponente

Als ersten Schritt entfernen die edit() Funktion aus der index.js Datei und verschieben den Code in eine eigene Datei. Die index.js hat dann also noch den folgenden Inhalt:

import { registerBlockType } from '@wordpress/blocks';
import './editor.scss';
import './style.scss';
import Edit from './edit';
import metadata from './block.json';

registerBlockType(
	metadata.name,
	{
		edit: Edit
	}
);

Wir importieren die neue Edit Komponente und weisen sie den Optionen des Blocks zu, während wir ihn registrieren. Wir entfernen weiterhin den Import für die ServerSideRender Komponente, da diese nicht mehr länger in der index.js Datei verwendet wird.

Implementieren der Komponente

Nachdem wir unsere neue Datei importiert haben, müssen wir in dieser die ServerSideRender Komponente erneut umsetzen und alle Dinge hinzufügen, wie wir für unsere Option brauchen. Das Ergebnis sieht wie folgt aus:

import ServerSideRender from '@wordpress/server-side-render';
import { InspectorControls } from '@wordpress/block-editor';
import { Disabled, PanelBody, RangeControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';

export default function Edit( props ) {
	const {
		attributes,
		setAttributes,
	} = props;
	const {
		postsToShow,
	} = attributes;

	return (
		<>
			<InspectorControls>
				<PanelBody
					title={ __( 'Settings', 'rpssrb' ) }
				>
					<RangeControl
						label={ __( 'Number of items', 'rpssrb' ) }
						value={ postsToShow }
						onChange={ ( value ) =>
							setAttributes( { postsToShow: value } )
						}
						min="1"
						max="100"
					/>
				</PanelBody>
			</InspectorControls>
			<div { ...useBlockProps() }>
				<Disabled>
					<ServerSideRender
						block="rpssrb/random-posts"
						attributes={ attributes }
					/>
				</Disabled>
			</div>
		</>
	);
}

OK, das ist jetzt natürlich sehr viel mehr als zuvor. Aber schauen wir uns jeden Teil einzeln an, um zu verstehen, was hier genau passiert.

Schritt 1: Komponenten importieren

In unserem neuen Code verwenden wir einige React-Komponenten. Diese erhalten wir aus verschiedenen WordPress-Packages, die wir alle in den Zeile 2-5 importieren.

Schritt 2: Erstellen einer Funktion und Verwendung der Komponenten-Properties

Wir erstellen zuerst einmal eine Funktion Edit und exportieren diese direkt wieder als Standard-Funktion. Damit kann sie dann in der index.js Datei verwendet werden, wie wir im ersten Code-Beispiel gesehen haben. Die „edit“ und „save“ Funktionen erhalten immer automatisch die Properties (Eigenschaften) übergeben. Wir können die Properties, die wir benötigen, dann gezielt „extrahieren“. Die attributes Property ist ein Objekt, welches alle Attribute zu einem spezifischen Block enthält. Es verwendet einen „React-State“, um die Benutzeroberfläche in Echtzeit zu aktualisieren, sobald sich einer der Werte ändert. Die setAttributes Property ist eine Funktion, die dazu verwendet wird, die Werte aus des attributes Objekts um „State-Objekt“ zu aktualisieren.

Da attributes auch wieder ein Objekt ist, können wir auch hier wieder ein paar Werte extrahieren und eigenen Variablen zuweisen. Wir machen das mit dem postsToShow, welches wir später verwenden werden.

Schritt 3: Die „Inspector-Controls“ implementieren

Nachdem wir nun das Attribut haben, welches wir ändern wollen, können wir das Kontrollfeld implementieren, mit dem es angepasst werden kann. Das Erste, was euch hier vielleicht auffällt und im ersten Moment merkwürdig vorkommen könnte, ist der „leere Tag“ <> in Zeile 12. Dieser ist notwendig, da die Funktion nur einen einzelnen Tag haben darf, der zurückgegeben wird. Wir verschachteln also die restlichen JSX-Tags in diesem einen leeren Tag.

Als Nächstes öffnen wir die <InspectorControls> Komponente, welche alle Elemente enthält, die in der Block-Sidebar angezeigt werden. Darin können wir <PanelBody> verwenden, damit unsere Option eine schöne Überschrift bekommt und zuletzt fügen wir mit <RangeControl> dann das Kontrollfeld hinzu, welches für die Anpassung der Option verwendet wird. Das Ergebnis sieht dann wie folgt aus:

The InspectorControls with a PanelBody and the title "Settings" as well as a RangeControl with the label "Number of item" with a selected value of 5.

Wir haben nun also einen schönen kleinen „Bereichs-Slider“, mit dem wir festlegen können, wie viele Posts angezeigt werden können. Mit den min und max Attributen können wir festlegen, in welchem Bereich dieser Wert liegen darf. Der Anfangswert (value) wird aus dem zuvor definierten postsToShow Attribut ausgelesen und bei einem onChange Event wird dann über die Funktion setAttribues der Wert im „State“ verändert. Einige Dinge werden euch nicht auf Anhieb klar sein, aber wenn ihr eine Zeit damit gearbeitet habt, werdet ihr es bald verstehen.

Schritt 4: Den Block rendern

Nachdem wir jetzt unser Kontrollfeld für die Einstellung der Anzahl an Posts haben, müssen wir diese Posts in unserer neuen Edit Komponente auch rendern. Hierzu verwenden wir erneut die <ServerSideRender> Komponente und übergeben jetzt zusätzlich die attributes an den Tag, damit sich die Komponente jedes Mal von selbst aktualisiert, wenn wir die Anzahl der anzuzeigenden Beiträge verändern.

Wir verschachteln das Ganz zusätzlich noch in einem <div>, welchem wir alle Block-Properties hinzufügen und zusätzlich noch in einer <Disabled> Komponente, damit wir unseren Block im Block-Editor auch einfach auswählen können (statt durch einen Klick einem der Post-Permalinks zu folgen).

Schritt 5: Registrierung der Attribute für den Block

Jedes Mal, wenn ihr in einem Block Attribute verwenden wollt, dann müsst ihr diese zuvor registrieren. Das wird in der block.js Datei gemacht. Die neue Datei sieht dann wie folgt aus:

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 2,
	"name": "rpssrb/random-posts",
	"version": "0.1.0",
	"title": "Random Posts",
	"category": "widgets",
	"icon": "smiley",
	"description": "An example for a ServerSideRender block using minimal ES6 code.",
	"supports": {
		"html": false
	},
	"textdomain": "random-posts-server-side-render-block-es6",
	"attributes": {
		"postsToShow": {
			"type": "number",
			"default": 5
		}
	},
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css"
}

Schritt 6: Verwendung der Option in der PHP-Callback-Funktion

Nachdem wir nun also unsere Option haben und diese auch speichern können, müssen wir sie auch in unserer PHP-Callback-Funktion verwenden. Aber glücklicherweise ist das recht einfach, denn die Option wird automatisch an unsere Funktion übergeben:

function rpssrb_render_callback( $atts ) {
	$args = [
		'post_type'      => 'post',
		'orderby'        => 'rand',
		'posts_per_page' => (int) $atts['postsToShow'],
	];

	// Run the query and render the posts as in the previous blog posts ...
}

Das war es auch schon! Ich würde euch nun empfehlen, euch einmal die verschiedenen Komponenten aus dem Block-Editor-Handbuch anzusehen. Da sind viele sehr nützliche Komponenten dabei, um Optionen für euren Block zu bauen. Denkt nur immer daran, auch die Attribute zu registrieren.

Fazit

Ich weiß, dass das sehr viel mehr Code war, als in den zwei vorherigen Beiträgen. Aber wenn ihr die „Inspector-Controls“ erst einmal grundlegen eingerichtet habt, könnt ihr recht einfach neue Kontrollfelder hinzufügen.

Wie ich bereits im ersten Beitrag dieser kleinen Serie beschrieben habe, könnte man die Dateien auch noch weiter aufteilen. Wenn beispielsweise die „Inspector-Control“ sehr komplex werden, dann möchtet ihr sie vielleicht in eine andere Datei verschieben. Aber in diesem kleinen Beispiel wollte ich es wieder so einfach wir möglich gestalten und habe sie bei einer edit.js Datei belassen.

Ein erster einfacher Block mit etwas ES6 Code – es ist nicht so furchteinflößend, wie es vielleicht klingt

Ich bin gerade im Urlaub und hatte ein paar technikfreie Tage, daher kommt mein Blog-Beitrag diese Woche ein wenig später. In meinem vorherigen Beitrag habe ich euch gezeigt, wie ihr einen Block ohne ES6/React Code schreiben könnt. Diese Woche möchte ich euch nun zeigen, was ihr mindestens tun müsste, um das volle Potenzial aus der modernen JavaScript-Entwicklung zu holen, ohne dabei zu komplex zu werden.

Erstellen eines Blocks mit dem „create-block“ Skript

Wie ich bereits im letzten Beitrag erwähnt hatte, würde ich euch sehr stark empfehlen, euch das create-block Paket anzusehen, mit dem ihr ein kleines Plugin erstellen könnt. Es erstellt eine ganz Menge von Dateien, mit denen ihr dann sofort loslegen könnt. Wir erstellen hier nun das gleiche Beispiel wie auch dem letzten Beitrag, indem wir innerhalb des wp-content/plugins Ordners den folgenden Befehl ausführen:

npx @wordpress/create-block random-posts-server-side-render-block-es6

Dies erstellt die folgenden Dateien im neu erstellten Plugin-Ordner:

$ tree
.
├── node_modules
├── package.json
├── random-posts-server-side-render-block-es6.php
├── readme.txt
└── src
    ├── block.json
    ├── edit.js
    ├── editor.scss
    ├── index.js
    ├── save.js
    └── style.scss

Im node_modules Ordner befinden sich alle Dateien, die für die Kompilierung von ES6 Code notwendig ist, damit der Browser diesen ausführen kann. Die package.json Datei definiert alle dazu notwendigen Abhängigkeiten sowie die Befehle, die ausgeführt werden.

Die Block-Definition

Die wichtigste Datei in einem „Block-Plugin“ ist die src/block.json Datei. In dieser werden Name, Titel, Icon und andere Details definiert:

{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 2,
    "name": "rpssrb/random-posts",
    "version": "0.1.0",
    "title": "Random Posts",
    "category": "widgets",
    "icon": "smiley",
    "description": "An example for a ServerSideRender block using minimal ES6 code.",
    "supports": {
        "html": false
    },
    "textdomain": "random-posts-server-side-render-block-es6",
    "editorScript": "file:./index.js",
    "editorStyle": "file:./index.css",
    "style": "file:./style-index.css"
}

Am Ende dieser Datei findet ihr drei sehr nützliche Zeilen. Diese definieren, wo die Asset-Dateien liegen. Während wir also im letzten Beitrag noch wp_enqueue_scripts verwendet haben, um die JavaScript-Datei zu laden, bekommen wir das nun einfach so dazu. Es ist also nicht mehr notwendig in PHP zu prüfen, ob die Datei existiert, diese mit ihren Abhängigkeiten zu laden und einen Timestamp für das Caching anzuhängen. Allein dieses kleine Feature ist es schon wert, dass man sich ein wenig mehr mit moderner Block-Entwicklung beschäftigt.

Ihr könntet nun weiterhin ES5 Code in diese JavaScript-Datei schreiben, aber sehen wir uns doch unser Beispiel mal mit ein wenig modernem JavaScript-Code an.

Registrierung des Server-Side-Rendered-Blocks mit ES6

Wie ihr oben im Auszug des Dateisystems gesehen habt, werden mit dem Skript drei JS Dateien erzeugt. Die index.js Datei registriert den Block. Die edit.js Datei ist zuständig für die Ausgabe des Blocks im Block-Editor, inkl. der Einstellungsoptionen. Die save.js wird abschließend verwendet, um den Block in den Inhalt des Beitrags zu speichern. Diese beiden zusätzlichen Dateien können verwendet werden, aber man kann auch alles in die index.js schreiben. Bei wirklich komplexen Blöcken würde man die Dateien sogar noch mehr aufspalten und beispielsweise die edit.js in kleinere (wiederverwendbare) Teile zerlegen. Aber um es weiterhin möglichst einfach zu halten, verwenden wir nur die index.js Datei:

import ServerSideRender from '@wordpress/server-side-render';
import { registerBlockType } from '@wordpress/blocks';
import './editor.scss';
import './style.scss';
import metadata from './block.json';

registerBlockType(
    metadata.name,
    {
        edit() {
            return (
                <ServerSideRender
                    block="rpssrb/random-posts"
                />
            );
        }
    }
);

Um eine Komponente oder Funktion in ES6 nutzen zu können, müssen wir sie zuerst importieren. Wir werden in dem Beispiel die Komponente ServerSideRender sowie die Funktion registerBlockType einsetzen. Wenn ihr den Code insgesamt mit unserem Beispiel von letzter Woche vergleicht, dann werdet ihr feststellen, dass er gar nicht so anders aussieht. Dieser „komisch aussehende HTML-Tag“ ist JSX, eine Erweiterung für HTML, die von React verwendet wird, um dessen Komponenten zu erstellen. Wenn man an diese Komponenten einen Parameter übergeben möchte, dann verwendet man dazu Attribute, genau wie in HTML (anstatt ein JSON-Objekt zu verwenden, wie in der ES5-Variante). In diesen Attributen kann man auch direkt JavaScript-Code verwenden. Für unsere ServerSideRender Komponente übergeben wir aber nur einen statischen Blocknamen.

Euch ist vielleicht ebenfalls aufgefallen, dass wir den Namen des Blocks für die registerBlockType Funktion aus dem metadata Objekt erhalten, das wir ebenfalls importieren. Das gibt uns die Möglichkeit, auf alle darin deklarierten Werte zuzugreifen. In diesem sehr einfachen Beispiel ist das nur der Name. Wir könnten sogar auch den Wert für das block Attribut der ServerSideRender dynamisch aus den Metadaten setzen, aber wir verwenden hier einen statischen String.

Die zwei Zeilen, die ich bisher noch nicht angesprochen habe, sind die Imports für die SCSS-Dateien. Wenn ihr das @wordpress/scripts Paket verwendet, das mit dem npx Befehl oben installiert wird, dann bekommt ihr eine Sass-Kompilierung einfach mit dazu. Ihr benennt einfach zwei Dateien mit editor.scss und style.scss und diese werden dann automatisch nur für den Block-Editor oder auch für das Frontend kompiliert.

Jetzt, nachdem ihr diesen schönen Code geschrieben habt, könnt ihr ihn endlich kompilieren. Wenn ihr das nur einmal machen wollt (in einer minifizierten Version, vorgesehen für den produktiven Einsatz), dann führt ihr npm run build aus. Während der aktiven Entwicklung verwendet ihr aber eher npm run start, um immer eine aktuelle (ausführliche und somit besser für das Debugging geeignete) Version zu haben, die den Code widerspiegelt, den ihr gerade schreibt.

Registrierung der PHP-Callback-Funktion für den Server-Side-Rendered-Block

Da wir hier noch immer einen Block schreiben, der seinen Inhalt aus dem PHP-Backend bekommt, müssten wir noch immer eine Callback-Funktion registrieren. Das sieht ziemlich ähnlich zu unserem vorherigen Beispiel aus. Allerdings verwenden wir hier die index.js Datei, um den Block in PHP zu registrieren und fügen die Callback-Funktion über die weiteren Parameter hinzu.

function create_block_rpssrb_block_init() {
	register_block_type(
		__DIR__ . '/build',
		[
			'render_callback' => 'rpssrb_render_callback',
		]
	);
}
add_action( 'init', 'create_block_rpssrb_block_init' );

Statt den Blocknamen zu verwenden, geben wir also nur den Pfad zum build Ordner an, der durch das Build-Skript erstellt wird. In diesem Ordner sucht PHP nach einer index.js Datei und darin dann nach dem Namen für den Block.

Wir verwenden auch weiterhin die gleiche Callback-Funktion aus dem vorherigen Beitrag. Auch hier können wir wieder den „Bonus“ haben, um die gleiche Callback-Funktion auch mit einem Shortcode zu verwenden.

Fazit

Es ist nicht wirklich schwer, einen Block mit ES6 Syntax zu schreiben. Wenn man dazu noch das create-block Paket (oder das @wordpress/script Paket) verwendet, wird auch der schwierige Teil für die Kompilierung sehr viel einfacher. Das Einzige, was ihr noch tun müsst – und das etwas schwerer sein kann – ist die Installation von node sowie die anschließende Installation von npm auf eurem System. Aber selbst dann müsst ihr dafür sogar, dann diese beiden immer aktuell und kompatibel mit den Paketen sind, die ihr verwendet. Das kann von Zeit zu Zeit schon etwas nervig sein.

Den Grund für all das werde ich in meinem nächsten Blogbeitrag behandeln, wenn wir ein paar einfache Einstellungsoptionen zum Block hinzufügen, um ihn noch besser nutzbar zu machen.

Einen Shortcode durch einen Block ersetzen, mit so wenig und so einfachem Code wie möglich

Mitte der Woche hatte Topher auf Twitter eine interessante Frage zum aktuellen Stand der Block-Entwicklung gestellt:

Click here to display content from Twitter.
Erfahre mehr in der Datenschutzerklärung von Twitter.

Übersetzung: Ich habe es mir schon lange nicht mehr angesehen, ist es schon so einfach einen Gutenberg Block zu schreiben wie einen Shortcode?

Das hat mich ein wenig zum Nachdenken gebracht, wie ich diese Frage übersetzen würde. Als ich meine ersten Blöcke geschrieben habe, waren es meisten „Server-Side-Rendered-Blocks“. Bei diesen Blöcken wird der Inhalt nicht direkt im Editor erzeugt, sondern von PHP-Code in einer Callback-Funktion gerendert, so wie man es von Shortcodes her kennt.

Der andere komplizierte Teil bei der Entwicklung von Blöcken ist die Notwendigkeit so viele neue Dinge zu lernen: React, JSX, Kompilierung von modernem JavaScript mit webpack und vieles mehr. Das alles zusammen macht es eben nicht „so einfach“ wie die Arbeit mit Shortcodes. Aber ich sollte dennoch einmal zeigen, wie man mit wenig Code einen Block entwickeln kann, der einen Shortcode ersetzt, ohne dabei all diese neuen Dinge lernen zu müssen.

Einen Server-Side-Rendered-Block entwickeln – der PHP Teil

Der einfachste Teil ist die Registrierung des Blocks und der Render-Callback-Funktion in PHP. Während man bei einem Shortcode die Funktion add_shortcode()für die Callback-Funktion verwenden würde, ist es ein Parameter bei der Registrierung eines Blocks in PHP:

function rpssrb_init() {
	register_block_type(
		'rpssrb/random-posts',
		[
			'render_callback' => 'rpssrb_render_callback',
		]
	);
}
add_action( 'init', 'rpssrb_init' );

Für diesen Blogbeitrag erstellen wir einen Block, der zufällig ein paar Beiträge rendert. Der Inhalt der Callback-Funktion ist hier sehr ähnlich zu der eines Shortcodes. Ihr könnt auch die gleichen Hilfsfunktionen wie bei einem Shortcode verwenden. Der Callback könnte wie folgt aussehen:

function rpssrb_render_callback( $atts ) {
	$atts = shortcode_atts(
		[
			'post_type'      => 'post',
			'orderby'        => 'rand',
			'posts_per_page' => 5,
		],
		$atts
	);

	$query = new WP_Query( $atts );

	$output = '';
	if ( $query->have_posts() ) {
		$output .= '<ul>';
		while ( $query->have_posts() ) {
			$query->the_post();
			$output .= sprintf(
				'<li><a href=%s>%s</a></li>',
				get_permalink(),
				get_the_title()
			);
		}
		$output .= '</ul>';
	}

	return $output;
}

Wir setzen ein paar Standard-Attribute und führen dann eine WP_Query aus, um 5 zufällige Beiträge zu bekommen. Diese geben wir dann in einer einfachen unsortierten Liste aus, wobei wir einfach den Titel ausgeben und auf den Beitrag verlinken.

Implementierung des Blocks in JavaScript – nur mit ES5 Code

Wie schon zuvor erwähnt ist das schwierigste bei der Block-Programmierung das Erlernen von React, JSX usw. und die Kompilierung davon. Wenn ihr euch damit noch nicht auskennt, kann es wirklich kompliziert werden. Daher zeige ich euch hier, wie ich mit etwas ES5 („altem JavaScript“) einen Block schreiben könnt. Das sieht im einfachsten Fall wie folgt aus:

wp.blocks.registerBlockType(
	'rpssrb/random-posts',
	{
		title: 'Random Posts',
		edit: function () {
			return wp.element.createElement(
				wp.serverSideRender,
				{
					block: 'rpssrb/random-posts'
				}
			);
		}
	}
);

Wir verwenden die wp.blocks.registerBlockType Funktion im ES5-Stil und geben ansonsten nur einen title und eine edit Funktion an. In dieser Funktion erstellen wir dann ein React-Element mit der wp.serverSideRender Komponente.

Die edit Funktion wird verwendet, um den Inhalt des Blocks im Editor anzuzeigen (in unserem Fall also das Ergebnis des PHP-Callback-Funktion). Wir brauchen diese Funktion nicht einmal zwingen, würden dann aber nichts im Block-Editor sehen (der Block würde aber auch keinen Raum einnehmen, da er leer ist), im Frontend würden wir aber die Ausgabe sehen.

Laden der JavaScript-Datei

Dieser JavaScript-Code muss nun geladen werden, wenn der Block-Editor verwendet wird. Falls ihr schon eine Datei habt, die im Block-Editor geladen wird, könnt ihr den Code in diese Datei speichern. Ansonsten ladet ihr die Datei einfach wie jede andere, verwendet dabei aber einen anderen Hook:

function rpssrb_register_scripts() {
	wp_enqueue_script(
		'random-posts-server-side-render-block',
		plugin_dir_url( __FILE__ ) . 'index.js',
		[ 'wp-blocks', 'wp-server-side-render' ],
		filemtime( plugin_dir_path( __FILE__ ) . 'index.js' ),
		true
	);
}
add_action( 'enqueue_block_editor_assets', 'rpssrb_register_scripts' );

In den Abhängigkeiten fügen wir noch wp-blocks und wp-server-side-render, wobei diese eigentlich normalerweise ohnehin geladen werden, wenn der Block-Editor verwendet wird.

Bonus: auch einen Shortcode verwenden

Da wir schon eine Callback-Funktion geschrieben, die genau so auch bei einem Shortcode genutzt werden kann, können wir mit einer einzelnen weiteren Zeile diese auch als Shortcode nutzbar machen (z.B. mit dem Shortcode-Block):

add_shortcode( 'rpssrb_random_posts', 'rpssrb_render_callback' );

Einschränkung: die Verwendung von Attributen ist kompliziert!

Der Block, den wir gebaut haben, kann keine Attribute verwenden. Diese zum Bock hinzuzufügen wäre einfach, dafür dann aber Kontroll-Elemente für eine einfache Verwendung zu implementieren ist nicht so einfach. Es wäre zwar mit ES5 möglich, aber ich würde dies nicht empfehlen.

Konsequenz: Learn JavaScript, deeply.

Wie uns Matt beim WCUS 1015 als Hausaufgabe mitgegeben hat, würde auch ich sehr empfehlen, mit dem Lernen von modernem JavaScript anzufangen. Der schwierigste Teil ist am Anfang dabei die Kompilierung mit webpack. Falls ihr gar keine Idee habt, wie ihr starten sollt, dann würde ich der Antwort von Birgit auf den Tweet zustimmen: verwendet das create-block Paket.

Mit dem create-block Paket könnt ihr nicht nur einen funktionierenden Block innerhalb eines WordPress Plugins erstellen, es installiert euch auch all die JavaScript-Pakete, die ihr für die Kompilierung von modernem JavaScript benötigt. Dazu verwendet es das @wordpress/scripts Paket und da Haupt-Skript, das ihr hiervon verwenden würdet, ist das start Skript. Versucht es einfach mal selbst aus und lest ein wenig in der Dokumentation, wie man Attribute hinzufügt, um euren Block noch einfacher nutzbar zu machen.

Fazit: einen Block zu erstellen ist einfach, aber es reicht eventuell nicht aus

Während ihr also einen Block schreiben könnt, der nur eine PHP-Datei und eine JavaScript-Datei mit ES5-Syntax benötigt, würde ich es auch nicht unbedingt empfehlen. Sobald ihr euch einmal mit den Grundlagen der Block-Erstellung vertraut gemacht habt, könnt ihr die Nutzbarkeit erheblich verbessern. Es klingt sicher am Anfang etwas einschüchternd und ihr werdet sicher in alle möglichen Fehler laufen, aber es ist es wert.

Falls ihr den vollständigen Code zu diesem Beitrag sehen wollt, dann findet ihr ihn auf GitHub als Plugin.