Entfernen des „Update Lock“ nach einer fehlgeschlagenen WordPress Aktualisierung

Das Aktualisieren von WordPress und seinen Plugins und Themes recht unkompliziert. Hierzu navigiert man zu „Dashboard | Aktualisierungen“ und aktualisiert hier die gewünschten Komponenten. Wenn während des Updates des Cores oder von Plugins/Themes ein Fehler passiert, dann kann WordPress dies meist abfangen und rückgängig machen, aber manchmal geht etwas schief.

Eine fehlgeschlagene Aktualisierung legt die Seite lahm

Wenn eine Aktualisierung abbricht, dann kann es sein, dass die Seite nicht mehr aufrufbar ist. In diesen Fällen erhält man die folgende Nachricht:

Wegen Wartungsarbeiten ist diese Website kurzzeitig nicht verfügbar. Schau in einer Minute nochmal vorbei.

Wenn WordPress den Core aktualisiert (oder mehrere Plugins), dann aktiviert es einen Wartungsmodus und schreibt einen Lock in die Datenbank.

Reparieren der Seite

Die zwei Sperren werden automatisch nach 15 Minuten entfernt. Da sie aber nicht nur das Backend, sondern auch das Frontend unerreichbar machen, möchte man sicher nicht 15 Minuten warten.

Deaktivieren des Wartungsmodus

Das Erste, was ihr tun solltet, ist nach einer Datei mit dem Namen .maintenance im Hauptverzeichnis eurer WordPress-Installation zu suchen. In dieser Datei befindet sich eine einzelne PHP-Zeile mit dem UNIX-Timestamp, der den Beginn des Installationsprozesses angibt:

<?php $upgrading = 1648994440; ?>

Wenn ihr diese Datei löscht, dann sollte die Nachricht verschwinden und ich solltet wieder das Frontend und Backend von WordPress sehen können.

Fortsetzen der vorherige Aktualisierung

Selbst nach dem Entfernen der .maintenance Datei kann es sein, dass ihr den WordPress-Core nicht direkt wieder updaten können. Wenn ihr ein erneutes Update versucht, könntet ihr auf diese Meldung stoßen:

Momentan wird eine andere Aktualisierung durchgeführt.

Dies wird durch die Option core_updater.lock hervorgerufen, die WordPress setzt, sobald der Update-Prozess startet:

$ wp option get core_updater.lock
1648994423

Wie ihr hier sehen könnt, ist der Zeitpunkt ein klein wenig früher (es ist der Zeitpunkt, bevor die ZIP-Datei heruntergeladen und entpackt wird). Wenn ihr euch also sicher seid, dass der vorherige Update-Prozess wirklich abgebrochen ist, dann könnt ihr die Option löschen, entweder über ein Datenbank-Tool oder über die WP-CLI:

$ wp option delete core_updater.lock
Success: Deleted 'core_updater.lock' option.

Jetzt solltet ihr die Aktualisierung erneut starten können, dieses Mal dann hoffentlich ohne weitere Fehler, die die Seite gleich wieder lahm legt.

Fazit

Obwohl WordPress-Updates normalerweise problemlos durchlaufen, kann es auch hier zu Fehlern kommen. In diesen Fällen ist es dann gut zu wissen, wie ihr die Seite direkt wieder zum Laufen bekommt und nicht 15 Minuten warten müsst, bis sich die Sperren von alleine lösen.

Ein defektes Git Repository Dateisystem reparieren

Vor ein paar Wochen hatte ich ein sehr ungewöhnliches Problem mit Git. In einem Repository, mit dem ich schon länger nicht mehr gearbeitet hatte, sollte ich mir mit git status ansehen, was ich zuletzt gemacht hatte, und bekam folgendes Ergebnis:

$ git status
error: Objektdatei .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 ist leer.
error: Objektdatei .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 ist leer.
fatal: Loses Objekt 6eeab7d4770c705a0491cafbc95830af69d5c6a2 (gespeichert in .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2) ist beschädigt.

Das hatte ich bisher noch nie bekommen. Zum Glück gibt es von Git einen Befehl, mit dem man den Zustand des Dateisystems überprüfen kann.

$ git fsck --full
error: Objektdatei .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 ist leer.
error: Konnte mmap nicht auf .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 ausführen.: Datei oder Verzeichnis nicht gefunden
error: 6eeab7d4770c705a0491cafbc95830af69d5c6a2: Objekt fehlerhaft oder nicht vorhanden: .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2
Prüfe Objekt-Verzeichnisse: 100% (256/256), fertig.
error: Objektdatei .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 ist leer.
error: Objektdatei .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 ist leer.
fatal: Loses Objekt 6eeab7d4770c705a0491cafbc95830af69d5c6a2 (gespeichert in .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2) ist beschädigt.

Das hat mir ein wenig mehr Informationen zum Fehler geben, aber leider noch immer keine Lösung dazu, wie es Git oft macht, wenn man ein Kommando verwendet.

Die Lösung

Mit diesen neuen Informationen konnte ich mich dann aber auf die Suche machen und eine Lösung bei Stack Overflow finden. Ich musste hierzu lediglich die beschädigte/leere Datei löschen. In meinem Fall war es wirklich nur diese eine Datei, wenn man aber in einem Git Repository mehrere solche Dateien hat, dann kann man mit dem folgenden Befehl recht schnell alle „leeren“ Dateien im Git-Ordner löschen:

$ find .git/objects/ -type f -empty | xargs rm

Nach der Ausführung dieses Befehls fehlen dann natürlich all diese Dateien im Repository. Diese bekommt man schnell wieder zurück, indem man sie von einem Remote lädt:

After this command, all corrupt files are missing from the repository. We can get them back by fetching the data from the remote:

$ git fetch -p
remote: Enumerating objects: 228, done.
remote: Counting objects: 100% (228/228), done.
remote: Compressing objects: 100% (91/91), done.
remote: Total 210 (delta 121), reused 188 (delta 99), pack-reused 0
Empfange Objekte: 100% (210/210), 90.23 KiB | 1.43 MiB/s, fertig.
Löse Unterschiede auf: 100% (121/121), abgeschlossen mit 11 lokalen Objekten.

Zuletzt können wir noch einmal eine Dateisystem-Prüfung machen und kontrollieren, ob nun alle Fehler behoben wurden:

$ git fsck --full
Prüfe Objekt-Verzeichnisse: 100% (256/256), fertig.
Prüfe Objekte: 100% (221/221), fertig.
blob c5446110414804bbba2a5316a3e996ff37666bb9 unreferenziert
blob 45dd1301284105bcfc7e183bc805b65bf1465f47 unreferenziert
blob 70376fcbe5060d0db11490249bed5b553c0d04cc unreferenziert

Fazit

Normalerweise gibt uns Git immer sehr hilfreiche Fehlermeldungen, wenn wir etwas falsch machen. In diesem Fall musste ich aber ein wenig recherchieren, um eine Lösung zu finden. Zum Glück hatten schon andere vor mir das gleiche Problem und konnten es lösen.

Unsere Lösung hat aber nur deshalb ohne Verluste funktioniert, weil wir die beschädigten/leeren Objekte von einem Remote neu laden konnten. Daher empfehle ich Leuten immer, dass sie ihren Code auch in einem Remote Repository haben sollten und häufigen Committen und Pushen.

Als Volunteer auf einem WordCamp

Heute möchte ich einen persönlichen Beitrag schreiben und einen Aufruf an alle Menschen in der Community machen. Ich möchte über meine Erfahrungen als Volunteer (freiwilliger Helfer) auf einem WordCamp sprechen.

Meine erste Erfahrung als Volunteer kam sehr spät. Ich habe mein erstes WordCamp 2010 besucht, schon 2012 eines organisiert und seither auf mehreren einen Vortrag gehalten. Diese Arten der Mithilfe sind auch sehr wichtig, aber sie sind ganz anders als die Mithilfe als Volunteer.

WordCamp Europe 2016 in Wien

Ich weiß nicht genau, wann ich zum ersten Mal daran gedacht habe, das WordCamp Europe nach Deutschland zu holen, aber 2015 habe ich mit einigen anderen Menschen aus der Community, mit dem damaligen Organisationsteam darüber gesprochen, wie es möglich werden könnte. Wenn man ein Event von dieser Größe organisieren möchte, dann ist es gut, ein wenig Erfahrung in der Organisation zu sammeln. Daher machte es für mich sehr viel Sinn, beim nächsten Event als Volunteer dabei zu sein.

Meine erste Schicht war als „Foyer Guard“ für den Raum der Vortragenden und der Kinderbetreuung, was sehr interessant war. Ich hatte nur auf dem WordCamp London zuvor gesehen, dass eine Kinderbetreuung angeboten wurde, aber ich habe erkannt, wie wichtig es für diejenigen war, die mit Kindern nach Wien gereist waren.

Meine zweite Schicht am ersten Tag war an der Registrierung. Dort konnte ich neue Teilnehmende begrüßen. Hier habe ich auch nicht alleine meinen Dienst verrichtet, sondern mit anderen zusammen. Diese Schicht hat also wirklich sehr viel Spaß gemacht, zum einen wegen der Arbeit mit den anderen Volunteers und zum anderen, weil ich so viele Menschen getroffen habe, einige davon kannte ich schon seit Jahren.

Am zweiten Tag war ich für die „Lunch Attendee Orientation“ eingeplant. Ich habe also den Teilnehmenden gezeigt, wo es Mittagessen gab, sollten sie es am ersten Tag nicht schon gefunden habe. Meine letzte Schicht war als „Door Guard“ für den großen Saal. Ich musste also dafür sorgen, dass alle, die zu spät kommen, beim betreten des Saals leise sind.

WordCamp London 2017, 2018 und 2019

Meine nächste Volunteering-Chance kam 2017 beim WordCamp London. Nur ca. einen Monat vor dem Event bat das Organisationsteam um Hilfe, weil einige Volunteers abgesagt hatten. Ich hatte zwar schon ein Ticket gekauft, aber dennoch gerne geholfen. Ich wurde auch zu einem tollen „WarmUp“ für Vortragende, Organisatoren und Volunteers eingeladen. In einer „Tischtennis-Bar“! Das hat super viel Spaß gemacht!

Nur ein Jahr später war ich wieder als Volunteer dabei, denn das WordCamp London ist eines meiner Lieblings-WordCamps. Dieses Mal gab es einen „Kids-Workshop“, das vom WordPress Mitbegründer Mike Little geleitet wurde. Ich war schwer beeindruckt, was mache Schulkinder in den wenigen Stunden erreichen konnten.

Ich habe auch beim WordCamp London 2019 ein drittes Mal mitgeholfen. Dort hatte ich meine erste Schicht im Lagerraum und der Garderobe. Dort habe ich den Sponsors geholfen ihre Boxen zu verstauen, nachdem sie ihre Stände aufgebaut hatten, und ich habe ein paar Swag-Beutel gepackt. Danach konnte ich ein wenig das WordCamp genießen und mir Sessions ansehen. Etwas später hatte mich dann die Haupt-Organisatorin abgefangen und sich bei mir entschuldigt. Ihr war nicht bewusst, dass sie den aktuellen Local-Lead-Organizer für das WordCamp Europe in den Lagerraum gesteckt hatte, als sie die Schichten für die Volunteers geplant hatte. Ich konnte sie aber beruhigen, dass es keinen Grund gäbe, sich zu entschuldigen. Ich was an diesem Tag einfach nur Volunteer und meine Hilfe wurde eben genau hier im Lageraum gebraucht und daher habe ich daher meine Schicht geleistet.

Fazit

Alle, die bei einem WordCamp als Volunteer helfen sind wichtig und das Organistationsteam ist ihnen für die Hilfe sehr dankbar, auch wenn die Aufgabe vielleicht klein und unbedeutend scheint. Die Mithilfe als Volunteer ist auch der erste Schritt zukünftig im Organisationsteam mitzuhelfen. Es gibt zwar keine Regel, dass man vorher Volunteer sein musste, aber es hilft auch jeden Fall, schon einmal einen ersten Einblick in die Organisation bekommen zu haben. Und auch als späteres Mitglied ist es gut zu wissen, wie sich die Rolle als Volunteer anfühlt, auch wenn man vielleicht noch nicht viel über WordPress und die Community weiß. Wenn ihr also ein WordCamp in eurer Nähe seht, das einen sogenannten „Call for Volunteers“ hat, dann überlegt doch mal, ob ihr nicht mithelfen wollt. Es gibt euch auf jeden Fall eine neue Perspektive auf das Event.

Und weil wir gerade von WordCamps sprechen, die Volunteers suchen: das WordCamp Europe findet im Juni statt und ihr könnt euch noch bis zum 31. März 2022 als Volunteer bewerben. Falls euch genau dieser Beitrag dazu bewegt hat, euch zu bewerben, dann würde ich mich freuen, wenn ihr mich auf dem „Social“ (das ist der Name für das „Warm-Up“ Event auf dem WordCamp Europe) ansprecht und eure Geschichte erzählt.

Scripts und Styles effektiv einbinden

Wenn man JavaScript und CSS Dateien einbinden möchte, dann sollte man immer die Funktionen wp_enqueue_script() und wp_enqueue_style() verwenden. Falls ihr das noch nicht tut, dann fangt bitte ab sofort damit an. Die Verwendung der beiden Funktionen ist wirklich sehr einfach, aber um diese auch effektiv zu nutzen, sollte man ein paar Dinge beachten.

Einfache Verwendung

Die einfachste Art, die Funktionen zu verwenden, ist die Definition eines „handle“ sowie einer URL der Datei, die ihr einbinden wollt:

wp_enqueue_script(
	'chart-js',
	'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js'
);

Dieses Beispiel würde eine JavaScript-Datei einbinden, sie sich auf einem externen Server befindet. Das ist so weit auch OK, aber es gibt viele Gründe, wieso man Dateien lieber lokal einbinden möchte, um beispielsweise Probleme beim Ausfall des CDN zu vermeiden oder aber, um den Datenschutz zu verbessern.

Verwendung einer lokalen Datei – schlechtes Beispiel

Um eine Datei aus einem Plugin oder Theme zu laden, würde es ausreichen, den Pfad wie folgt relative anzugeben:

wp_enqueue_script(
	'chart-js',
	'/wp-content/plugins/plugin-name/assets/chart.min.js'
);

Die Datei würde dann wie folgt in den Quellcode eingebunden werden:

<script type='text/javascript' src='https://theme-tests.docker.test/wp-content/plugins/plugin-name/assets/chart.min.js?ver=5.9'></script>

Das würde zwar funktionieren, es gibt mit dieser einfachen Methode aber ein paar Probleme.

1. Immer dynamische Pfade verwenden

Im Beispiel oben wurde ein relativer Pfad auf das Verzeichnis wp-content/plugins verwendet. Das mag für viele von euch richtig aussehen, aber der Ordner wp-content kann einen anderen Namen haben (und manche Sicherheits-Plugins tun das auch – obwohl es eine schlechte Idee ist). Ihr solltet daher immer eine Hilfsfunktion verwenden, um den relativen Pfad zu diesem Ordner zu erhalten. Wenn ihr eine Datei in einem Plugin oder Theme einbindet, dann könnt ihr hierzu verschiedene Funktionen verwenden. Diese hier werdet ihr dafür vermutlich in der Regel verwenden:

2. Immer eine Version der Datei angeben

Wie ihr in dem Beispiel oben sehen könnt, fügt WordPress immer eine Versionsnummer an. Wenn ihr selbst keine festlegt, dann wird WordPress immer seine aktuelle Version ans Ende der URL anhängen. Heute würde also 5.9 am Ende stehen. Diese Versionsnummer hat den Zweck, euch beim Caching zu helfen. Da sich eure Dateien aber sicher nicht (nur) dann ändern, wenn eine neue WordPress Version erscheint, solltet ihr hier einen anderen Versions-String verwenden. Das könnte einer der folgenden sein:

  • Eine statische Versionsnummer, die der Version des Plugins entspricht
  • Ein statisches Änderungsdatum des Plugins
  • Ein statisches Änderungsdatum der Datei, die eingebunden werden soll
  • Ein dynamisches Änderungsdatum der Datei, die eingebunden werden soll

In der Vergangenheit habe ich oft die erste oder dritte Option verwendet. Aber dabei musste jeder dieser Versions-String (für alle Dateien) immer manuell aktualisiert werden. Dabei vergisst man dann schnell mal einen und der Browser liefert eine alte Datei aus seinem Cache aus. Heutzutage verwende ich daher normalerweise den letzten Ansatz. Das hat auch den Vorteil, dass während der Entwicklung, bei jedem Speichern der Datei immer sichergestellt wird, dass der Browser stets die aktuellste Datei lädt. Ein Beispiel für diesen Ansatz – kombiniert mit dynamischen Pfaden – könnte wie folgt aussehen:

wp_enqueue_script(
	'chart-js',
	plugins_url( 'assets/chart.min.js', __FILE__ ),
	array(),
	filemtime( plugin_dir_path( __FILE__ ) . 'assets/chart.min.js' )
);

Das Resultat der Einbindung der Datei würde dann so aussehen:

<script type='text/javascript' src='https://theme-tests.docker.test/wp-content/plugins/plugin-name/assets/chart.min.js?ver=1644792351' id='chart-js-js'></script>

Wie ihr hier sehen könnt, wurde an das Ende der URL der aktuelle UNIX-Timestamp angehängt. Da sich dieser mit jeder Änderung der Datei ändert, wird der Browser immer die aktuellste Datei laden.

Fazit

Dieser Blogbeitrag behandelt ein sehr einfaches Konzept, aber ich sehe immer wieder Plugins und Themes, die Dateien nicht effektiv einbinden. Mit diesen kleinen Tricks könnte ihr viel Stress beim Finden von Fehlern vermeiden, die nur deshalb auftreten, weil der Fehler eigentlich nur darin bestand, dass der Browser noch immer eine alte Version der Datei aus seinem Cache geladen hatte.

Dynamische Formular-Hooks für GravityForms

Ich nutze wirklich sehr gerne GravityForm, wenn ich sehr dynamische Formulare erstellen muss. Der Formular-Builder ist sehr umfangreich und bietet viele Möglichkeiten. Manchmal muss man dann aber doch ein wenig eigenen Code schreiben und sich per Hook in die Formular-Abarbeitung einhängen. In diesem Blogbeitrag möchte ich euch zeigen, wie das auch über mehrer Installation eines Formulars hinweg funktioniert.

Verwendung eines Hooks für alle Instanzen

Nehmen wir als Beispiel mal den Hook gform_pre_submission. Wenn ihr euch hier einhängen wollt, dann funktioniert das, wie bei jedem anderen Hook in WordPress auch:

function my_pre_submission( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission', 'my_pre_submission' );

Dieser Code-Schnippsel würde den Wert jedes Formular-Feldes mit der ID 1 in jedem Formular überschreiben. Das ist sicher nicht, was ihr normalerweise wollt.

Verwendung eines Hooks nur für ein Formular

Wenn ich den Wert eines Feldes ändern wollt, dann in der Regel nur für ein spezifisches Formular. Dies kann recht einfach erreicht werden, indem ihr die Formular-ID an das Ende des Hooks anhängt:

function my_pre_submission( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_1', 'my_pre_submission' );

Nun wird nur noch das Feld für das Formular mit der ID 1 überschrieben, sobald es übermittelt wurde. Das ist eine gute Lösung, bis zu dem Zeitpunkt, an dem ihr die Export/Import Funktion verwendet.

Umgang mit Formularen mit verschiedenen IDs

Sagen wir einmal, ihr habt ein Formular in einem Projekt erstellt und möchtet dieses nun in einem anderen verwenden. Hierzu könnt ihr dann einfach die Funktion für den Export und Import verwenden, was das Formular mit allen Einstellungen (aber ohne die Einträge) kopiert. Aber wenn das neue Projekt bereits Formulare hat, dann kann es passieren, dass das Formular hier eine andere ID bekommt. Das gleicht passiert auch recht schnell, wenn ihr in einer Entwicklungsumgebung ein Formular erstellt und es dann auf eine Live-Seite importiert oder aber wenn ihr nicht alleine neue Formulare erstellt.

In diesen Fällen müsst ihr dann die neue Formular-ID rausfinden und den Wert entsprechend in allen Hooks anpassen. Aber was ist, wenn ihr den Code in einer Versionsverwaltung habt und nicht einfach einen neuen statischen Wert verwenden könnt?

Verwendung einer Konstanten für die Formular-ID

In einem Projekt hatte ich genau dieses Problem und mich dazu entschieden, die ID des Formulars einfach in einer Konstanten zu speichern. Alle Hooks sehen dann in etwa wie folgt aus:

function my_pre_submission_contact( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_' . PREFIX_CONTACT_FORM_ID, 'my_pre_submission_contact' );

function my_pre_submission_form_event( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_' . PREFIX_EVENT_FORM_ID, 'my_pre_submission_form_event' );

Das ist natürlich nicht der echt Code, aber ich denke, ihr versteht den Ansatz damit. Ich habe also eine Konstante pro Formular (und hierbei den Formularnamen und nicht dessen ID verwendet). Damit kann dann die ID je nach System dynamisch gesetzt werden.

Setzen eines Standard-Werts für die Konstante im Plugin (oder Theme)

Ich speichere solchen Code normalerweise in einem Plugin. In der Hauptdatei dieses Plugins setze ich dann einen Standardwert, der dem System entspricht, auf dem ich das Formular zuerst verwendet/erstellt habe:

if ( ! defined( 'PREFIX_CONTACT_FORM_ID' ) ) {
	define( 'PREFIX_CONTACT_FORM_ID', 1 );
}
if ( ! defined( 'PREFIX_EVENT_FORM_ID' ) ) {
	define( 'PREFIX_EVENT_FORM_ID', 2 );
}

Mit der ! defined() Prüfung kann ich feststellen, ob die Konstante schon zuvor an andere Stelle gesetzt wurde und ansonsten den Standardwert setzen. Im zweiten Projekt kann ich daher dann in der wp-config.php Datei die anderen IDs setzen:

define( 'PREFIX_CONTACT_FORM_ID', 4 );
define( 'PREFIX_EVENT_FORM_ID', 5 );

Fazit

Obwohl GravityForms wirklich sehr viele Hooks anbietet und diese wirklich einfach genutzt werden können, kann es aufgrund der statischen IDs in den Namen schnell zu Problemen führen. Die Verwendung von dynaischem ID-Werten in Konstanten ermöglicht es euch, den selben Code in mehreren Projekten zu verwenden.

Das Gleiche gilt selbstverständlich auch für alle anderen Hooks von GravityForms, bei denen eine ID ans Ende des Hooks gehängt werden können, um nur bestimmt Formulare, Felder, etc. anzupassen.

Kategorie-Seiten alphabetisch sortieren

In einer Facebook-Gruppe wurde gefragt, wie man alle Beiträge auf einer Kategorieseite alphabetisch sortieren kann. In diesem Beitrag möchte ich darauf eingehen, wie man die Sortierung von Beiträgen auf Archiv-Seiten von Kategorien (und anderen) anpassen kann.

Sortierung von Beiträge auf allen Kategorie-Seiten

Selbst wenn ich nicht genau weiß, wieso die Person die Beiträge auf allen Kategorie-Seiten (und nicht etwa auf einer Archivseite eines anderen Post-Types) sortieren wollte, wäre das hier der passende Code dafür:

function aa_sort_all_archives( $query ) {
	// Only sort main query.
	if ( ! $query->is_main_query() ) {
		return;
	}
	// Only sort category archives.
	if ( ! is_category() ) {
		return;
	}

	$query->set( 'order', 'ASC' );
	$query->set( 'orderby', 'post_title' );
}

add_action( 'pre_get_posts', 'aa_sort_all_archives' );

Wann immer man die Query verändern möchte, sollte man eine Callback-Funktion zum pre_get_posts Hook verwenden. In dieser Callback-Funktion prüfen wir zuerst einmal, ob wir uns in der Haupt-Query befinden. Dann prüfen wir mit einer weiteren Bedingung nach für unseren Anwendungsfall. Ist diese Bedingung nicht erfüllt, verlassen wir die Funktion ebenfalls. Wenn alle Bedingungen erfüllt sind, dann passen wir die Query an. In Zeile 7 prüfen wir also, ob wir auf einer Kategorie-Seite sind. Wenn das der Fall ist, dann setzen wir die Sortierreihenfolge auf aufsteigend („ascending“) in Zeile 11 und das Feld, nach dem sortiert werden soll in Zeile 12 auf das post_title Feld. Das war’s!

Nur Beiträge in einer bestimmen Kategorie ordnen

Für manche Post-Types ist es wohl eher unwahrscheinlich, dass man die Posts nach für alle Kategorien sortieren möchte. Daher können wir auch den „slug“ (oder den Namen bzw. die ID) einer Kategorie an die is_category() Funktion übergeben. In diesem Beispiel habe ich eine spezielle Kategorie „alphabetical“ verwendet, um nur auf dieser Kategorieseite die Beiträge zu sortieren:

function aa_sort_alphabetical_archives( $query ) {
	// Only sort main query.
	if ( ! $query->is_main_query() ) {
		return;
	}
	// Only sort category archives.
	if ( ! is_category( 'alphabetical' ) ) {
		return;
	}

	$query->set( 'order', 'ASC' );
	$query->set( 'orderby', 'post_title' );
}

add_action( 'pre_get_posts', 'aa_sort_alphabetical_archives' );

In der gleichen Art und Weise lassen sich auch viele andere Archiv-Seiten unter Verwendung der „Conditional Tags“ überprüfen. Im CODEX findet ihr außerdem eine Seite über „Alphabetizing Posts“, darin wird aber die Verwendung einer sekundären Query gezeigt. Macht so etwas bitte niemals! Und lasst auch nicht alle Beiträge ausgeben! Macht also nichts von dem, was die Seite euch hier erzählt 😉

Fazit

Das Sortierung von Beiträgen (oder anderen Beitragstypen) kann auf einer Archiv-Seite am besten mit dem pre_get_posts Hook gelöst werden. Wenn ihr das erste Mal mit diesem Hook arbeitet, dann kann es passieren, dass es nicht gleich so funktioniert, wie ihr es gerne hättet, aber es lohnt sich wirklich sich mit dem Hook zu beschäftigen.

Ihr findet den Code zu diesem Beitrage auch als funktionierendes Plugin in einem GIST, wo ihr es euch auch als ZIP-Datei herunterladen und dann auf eurer Seite installieren könnt.

2022: Ein ereignisreiches Jahr

Ich veröffentliche normalerweise keine „Neujahrs“-Beiträge – ich habe normalerweise nur einen Beitrag zum Blog-Geburtstag – aber da noch immer die letzte Kalenderwoche von 2021 ist und ich nicht sicher bin, ob ich noch einen #projekt26 Beitrag veröffentlichen muss, möchte ich euch ein wenig über das neue Jahr aus meiner Sicht erzählen.

#projekt26 geht weiter

Das ist entweder der letzte Blogbeitrag des vergangenen Jahres oder der erste des neuen 😉 Auch wenn dieses Jahr einiges bei mir ansteht, möchte ich weiterhin versuchen alle zwei Wochen einen Beitrag zu veröffentlichen. Ich werde auch versuchen auf anderen Blogs zu kommentieren, auch wenn ich nicht einmal dazu komme genügend Beiträge zu lesen.

WordPress Releases und Full Site Editing (FSE)

Dieses Jahr könnte das erste sein, in dem es vier Hauptversionen von WordPress geben wird. Mit der anstehenden Version 5.9 in diesem Monat wird es auch ein neues Standard-Theme geben: TwentyTwentyTwo! Daher werde ich mir dieses Jahr endlich mal mein erstes FSE-Theme vornehmen. Entweder in einem Projekt oder aber, indem ich mein aktuelles Theme komplett neu schreibe.

Ein Plugin geht in Rente

Mit der Veröffentlichung von 5.9 wird es auch ein neues Feature geben: den Sprachumschalter auf der Login-Seite. Damit wird einer meiner ersten beiden Plugins endlich in Rente geschickt.

WordCamp Europe 2022 … in Porto!

Das große „Ereignis“ wird für mich das WordCamp Europe 2022 werden, das endlich in Porto stattfinden wird. Unser Organisationsteam arbeitet hart daran, die Community wieder zusammenzubringen. Da aber Corona noch immer da sein wird, werden wir sicherstellen, dass es so sicher wie möglich für alle sein wird. Nach dieser Auflage werde ich dann auch erst einmal von der Organisation des WCEU zurücktreten – zumindest für eine Zeit.

Fazit

Die letzten beiden Jahre waren schwer für viele von uns. Aber 2022 sieht nach dem Jahr aus, in dem wir endlich wieder viele Dinge tun können, die wir so sehr lieben. Ich kann es kaum erwarten viele von euch in Porto wiederzusehen oder auf einem anderen WordCamp dieses Jahr. Mit FSE am Horizont wird es sicher auch einige neue und spannende Themen geben, über die ich schreiben kann.

Block Patterns für neue Einträge eines Custom Post Types nutzen

In Projekten nutze ich sehr häufig Custom Post Types (CPT). In einem neuen Projekt wurde der Inhalt der Einträge komplett mit Core-Blöcken gebaut. Hierzu habe ich in der Vergangenheit normalerweise Post-Meta-Felder und ein festes Seitentemplate verwendet. Aber da die Erstellung solch komplexer Inhalte für einen CPT recht viel Arbeit und auch sehr fehleranfällig ist, wollte ich für die neuen Einträge ein Template verwenden.

Erstellung eines Block Pattern

Die einfachste Art ein Pattern für einen Post-Type zu erstellen ist es, den Inhalt zuerst mit dem Block-Editor zu erstellen und das HTML Markup dann über die „Kompletten Inhalt kopieren“ in die Zwischenablage kopieren:

Jetzt könnt ihr dieses Markup verwenden, um ein Block Pattern mit diesem Standardinhalt für neue Inhalte zu erstellen:

function cpt_register_block_pattern() {
	register_block_pattern(
		'my-cpt/my-cpt-pattern',
		array(
			'title'       => __( 'My CPT pattern', 'my-cpt' ),
			'description' => _x( 'The custom template for my_cpt', 'Block pattern description', 'my-cpt' ),
			'content'     => '
<!-- wp:paragraph {"placeholder":"Custom Post Type ..."} -->
<p></p>
<!-- /wp:paragraph -->

<!-- wp:columns -->
<div class="wp-block-columns"><!-- wp:column -->
<div class="wp-block-column"><!-- wp:image -->
<figure class="wp-block-image"><img alt=""/></figure>
<!-- /wp:image --></div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column"><!-- wp:heading {"placeholder":"Name ..."} -->
<h2></h2>
<!-- /wp:heading -->

<!-- wp:paragraph {"placeholder":"Phone ..."} -->
<p></p>
<!-- /wp:paragraph --></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->',
		)
	);
}
add_action( 'init', 'cpt_register_block_pattern' );

Erstellung eines Templates für neue Einträge

Wenn man einen neuen CPT registriert, dann kann man auch Standardinhalte definieren, indem man das template Argument verwendet und darin in einem mehrdimensionalen Array ein Template definiert:

function cpt_register_post_type() {
	register_post_type(
		'my_cpt',
		array(
			'label'                 => __( 'Custom Post Type', 'my-cpt' ),
			'supports'              => array( 'title', 'editor' ),
			'public'                => true,
			'show_ui'               => true,
			// ... other settings
			'has_archive'           => true,
			'show_in_rest'          => true,
			'template'              => array(
				array(
					'core/paragraph',
					array(
						'placeholder' => __( 'Custom Post Type ...', 'my-cpt' ),
					),
				),
				array(
					'core/columns',
					array(),
					array(
						array(
							'core/column',
							array(),
							array(
								array(
									'core/image',
								),
							),
						),
						array(
							'core/column',
							array(),
							array(
								array(
									'core/heading',
									array(
										'level'       => 2,
										'placeholder' => __( 'Name ...', 'my-cpt' ),
									),
								),
								array(
									'core/paragraph',
									array(
										'placeholder' => __( 'Phone ...', 'my-cpt' ),
									),
								),
							),
						),
					),
				),
			),
		)
	);
}
add_action( 'init', 'cpt_register_post_type' );

Wie ihr schon an diesem kleinen Beispiel erkennen könnt, kann ein solches Array schnell sehr komplex, unleserlich und schwer zu pflegen werden. Ihr müsstet außerdem das HTML des Block Pattern in das PHP Array Format konvertieren. Jede Änderung am Pattern würde also eine doppelte Anpassung notwendig machen, bei der man schnell mal einen Fehler macht.

Block Patterns und Custom-Post-Types kombinieren

Da mir die Lösung mit dem PHP Array nicht wirklich gefallen hat (das Template in dem Projekt war noch viel komplexer als das aus dem Beispiel oben), habe ich zuerst das folgende versucht:

// ...
'template'              => array(
	array(
		'my-cpt/my-cpt-pattern',
	),
),
// ...

Ich hatte gedacht, dass ich vielleicht einfach ein Block Pattern im template verwenden kann und nicht nur Blöcke. Aber leider hat das nicht funktioniert. Bei der Suche nach einer Lösung bin ich auf verschiedene GitHub issues gestoßen. Dort habe ich den core/pattern Block gefunden, mit dem es dann ganz einfach war:

'template'              => array(
	array(
		'core/pattern',
		array(
			'slug' => 'my-cpt/my-cpt-pattern',
		),
	),
),

Leider ist der core/pattern Block aber noch nicht in WordPress 5.8 vorhanden. Man kann ihn aber einsetzen, indem man das Gutenberg Plugin installiert und aktiviert. Ich hoffe aber sehr, dass es mit WordPress 5.9 verfügbar sein wird, was nächsten Monat erwartet wird.

Fazit

Block Patterns und Templates sind bei der Arbeit mit Custom-Post-Types wirklich hilfreich. Wann man beide mit dem core/pattern Block kombiniert, dann kann es die Arbeit wirklich sehr erleichtern.

PHP Funktionen in WordPress debuggen

Wenn man ein Plugin oder Theme entwickelt, dann möchte man manchmal eine spezielle Funktion debuggen. Diese Funktion wird eventuell in einem größeren Kontext verwendet, was das Debuggen recht schwer und/oder langsam macht. Schauen wir uns hierzu das folgende Beispiel für eine Callback-Funktion eines Shortcode (oder „Server-Side-Rendered“ Blocks an):

function my_shortcode_callback( $atts ) {
	$atts = shortcode_atts( array(
		'post_id' => '',
		// ...
	), $atts, 'my_shortcode' );

	$result = my_function_to_debug( $atts );

	return sprintf(
		__( 'Posted on %1$s by %2$s', 'my-textdomain' ),
		date_i18n( get_option( 'date_format' ), $result['date'] ),
		get_the_author_meta( 'display_name', $result['author'] )
	);
}
add_shortcode( 'my_shortcode', 'my_shortcode_callback' );

In diesem Callback rufen wir die Funktion auf, die wir debuggen wollen. Diese Funktion kann sehr einfach, aber auch sehr komplex sein. Nehmen wir einfach einmal diese hier als Beispiel:

function my_function_to_debug( $atts ) {
	$post = get_post( (int) $atts['post_id'] );
	// Maybe do something with the post and create some result
	// ...

	// Dummy result: select two values from the post
	$result = [
		'date'   => $post->post_date,
		'author' => $post->post_author,
	];

	return $result;
}

Wenn man das auf den Weg würde man nun eine Seite oder einen Beitrag anlegen und dort den Shortcode einbinden, damit die Funktion, die wir debuggen wollen, aufgerufen wird. Nehmen wir mal an ihr wollt dabei den Aufruf der Funktion mit verschiedenen Parametern testen. Dann müsstet ihr mehrere Shortcodes in Seite oder Beitrag einfügen oder sogar mehrere Seiten/Beiträge anlegen.

Debuggen mit der WP-CLI

Eine Methode, sich ich in einem solchen Fall gerne verwende, ist die Nutzung der WP-CLI. Hier kann man beliebigen Code mit wp shell Befehl „in einer WordPress-Umgebung“ ausführen lassen. So könnte das aussehen:

$ wp shell
wp> my_function_to_debug( [ 'post_id' => 1 ] );
=> array(2) {
  ["date"]=>
  string(19) "2021-09-26 21:57:32"
  ["author"]=>
  string(1) "1"
}

Nach dem Starten der Shell rufen wir einfach die Funktion auf und übergeben direkt die passenden Argumente, die wir für unseren Test benötigen. Da die Funktion ein Array zurückgibt, erhalten wir ein Ergebnis in Form einer var_dump Visualisierung.

In gleicher Weise können wir natürlich auch die Callback-Funktion des Shortcodes selbst, aber auch jede andere Funktion eines Plugins/Themes oder des WordPress Core ausführen:

$ wp shell
wp> my_shortcode_callback( [ 'post_id' => 1 ] );
=> string(34) "Posted on 5 December 2021 by wapuu"

Der Vorteil des Debuggings mit dieser Technik ist, dass hierbei keine (Frontend) Seite gerendert werden muss. Es lädt stattdessen „nur“ die WordPress-Umgebung und führt die Funktion direkt aus. Das ist also sehr viel einfacher und schneller. Es muss eben auch keine Seite erstellt und der Shortcode darin eingefügt werden.

Debugging der Funktion mit einem AJAX Callback

Das einzige Problem an diesem Ansatz ist, dass es nicht (einfach) möglich ist, beim Debugging auch XDEBUG zu verwenden, da XDEBUG in der Regel nur in Verbindung mit einem HTTP-Request funktioniert. Ihr könntet zwar einen solchen Request mit curl auch in einem Terminal ausführen, aber dann müsstet ihr noch immer eine Seite oder einen Beitrag erstellen und dort den Shortode mit den Parametern einfügen. Stattdessen könnt ihr einen AJAX-Callback „missbrauchen“, um die Funktion in einem HTTP-Request auszuführen:

function my_ajax_callback() {
	$result = my_function_to_debug( [ 'post_id' => 1 ] );
	var_dump( $result );
}
add_action( 'wp_ajax_my_ajax_action', 'my_ajax_callback' );

Wenn ihr nun einen HTTP-Request auf die URL /wp-admin/admin-ajax.php?action=my_ajax_action macht (entweder im Browser oder im Terminal), dann erhaltet ihr die gleiche var_dump Ausgabe. Dieses Mal könnt ihr dann Breakpoint für XDEBUG für das Debugging setzen.

Fazit

Das Debugging von Funktionen benötigt nicht immer ein komplexes Anlegen von Seiten/Beiträgen, um die zu testende Funktion aufzurufen. Mit Hilfe der hervorragenden WP-CLI oder einem AJAX-Callback könnt ihr dies viel einfacher und schneller erledigen.

Video-Dokumentation einer WordPress-Installation im Backend

Während ich letzte Woche an einer Website gearbeitet habe ist mir etwas aufgefallen, dass ich gerne mich euch teilen möchte. Auf dem Dashboard habe ich ein Widget entdeckt, in das ein YouTube-Video einer aufgezeichneten Schulung eingebunden war.

Hinzufügen eines Videos zum Dashboard

Die Lösung auf dieser Seite war ein eigenes Dashboard-Widgets. Die URL zum Video wurde hierbei auf einer Einstellungsseite der Agentur eingetragen, auf der auch viele andere Einstellungen für die vielen Features der Installation gesteuert werden konnten.

Ich fand dieses Video eine super Idee und mein erst Impuls war es natürlich, selbst so etwas zu programmieren. Aber „leider“ gibt es ja für fast alles in WordPress bereits von jemand anderem ein Plugin. Statt also ein eigenes Plugin zu programmieren habe ich potenzielle Plugins für eine solche Funktionalität gefunden. Das einfachste ist Video Dashboard von Brian Johnson. Hier kann man bis zu 50 Videos von YouTube oder Vimeo einstellen und auch festlegen, welche Rolle jemand haben muss, um die Videos sehen zu können. Ein ähnliches Plugin ist Videos on Admin Dashboard. Hier können aber nur zwei Videos von YouTube oder Vimeo eingestellt werden, ebenfalls mit einer Steuerung der Rolle. Interessanterweise verwendet dieses Plugin wohl die gleichen Namen für die Einstellungen, denn hatte man beim anderen Plugin bereits Video-URLs hinterlegt, dann werden diese auch in diesem Plugin verwendet.

Fazit

Ein Video auf dem Dashboard zu platzieren ist eine ziemlich clevere Art den Menschen, die eine WordPress-Installation verwenden müssen, bei der Arbeit zu helfen. Wenn ihr dabei eine Plattform wie YouTube oder Vimeo nutzt, dann solltet ihr bei (individuellen) Videos sicherstellen, dass diese „nicht gelistet“ sind oder sogar nur auch einer bestimmten URL angesehen werden können (z.B. mit Vimeo Pro).

Wie bietet ihr Dokumentationen für WordPress-Installationen an? Verwendet ihr auch (gehostete) Video, ein anderes Dokumentations-Plugin oder ein externes Dokumentations-Tool?