Detailliert Download-Statistiken für Plugins abrufen

Vor kurzem habe ich die Download-Zahlen für ein Plugin im offiziellen WordPress.org Plugin-Directory nachgeschlagen. Unter „Erweiterte Ansicht“ eines jeden Plugins findet man eine Grafik mit den „Downloads pro Tag“ und den Zahlen aus den letzten 267 Tagen. Sehen wir uns diesen Graphen für das beliebte Antispam Bee Plugin an:

Downloads pro Tag für Antispam Bee

Wenn ihr mit der Maus über den Graphen fahrt, dann findet ihr zu jedem Tag die entsprechenden Downloads von diesem Tag. Ich wollte aber die Summe der Downloads aus den letzten paar Wochen nach der letzten Veröffentlichung wissen (die Spitze im Graphen) und diese manuell niederzuschreiben und zusammenzurechnen wäre dann doch ein wenig zu viel Arbeit gewesen. Wie komme ich also leichter an diese Zahlen heran?

Weiterlesen →

Bessere Sicherheit für WordPress mit sicheren Server-Headern

Es gibt viele Möglichkeiten eine WordPress Website sicherer zu machen. Für manches davon würdet ihr vermutlich zusätzliche Plugins verwenden. Aber es gibt auch einfache Wege zur Erhöhung der Sicherheit, bei denen ihr nur ein paar Server-Einstellungen ändern müsst. Aber bevor ich ins Detail gehe möchte ich erst einmal zeigen, wie man den aktuellen Stand der Sicherheit eurer Seite herausfinden kann.

Mozilla Observatory

Ein hervorragendes Tool zum Testen der Sicherheit einer Website ist das Mozilla Observatory Tool. Dieses Tool scannt nicht nur die Header, die der Server sendet, es analysiert auch TLS und SSH Einstellungen und verwendet einige dritte Tools für einen guten Überblick. Ein Ergebnis eines solchen Scan, bevor Anpassungen gemacht werden, könnte wie folgt aussehen:

Weiterlesen →

Speicherverbrauch debuggen

Letzte Woche wurde ein Problem mit einer Seite berichtet. Dort wurde manchmal auf einer Seite ein 500er Fehler angezeigt, aber leider nicht immer. Nach der Aktivierung des WP_DEBUG Modus konnte ich sehr schnell feststellen, dass das Speicherlimit überschritten wurde. Die einzige Lösung war es, das Speicherlimit zu erhöhen, da die Funktionalität auf dieser Seite viel Speicher brauchte und es keine schnelle Optimierung gab.

In solchen Fällen wäre es dann schön, wenn man rausfinden könnte, wie hoch das Limit erhöht werden muss. Wie viel Speicher verbraucht also eine Seite normalerweise. Es gibt mehre Wege, dies herauszufinden.

Der gefährliche Weg

Eine sehr einfache Lösung wäre es das Speicherlimit erst einmal zu verringern, bis der Fehler jedes Mal auftritt. Dann erhöht man das Limit schrittweise, bis der Fehler nur noch selten auftritt. Dies ist der Wert, um den herum sich der eigentliche Verbrauch bewegen wird. Zu diesem Wert fügt man noch etwas Puffer hinzu, und prüft, ob der Fehler nun nicht mehr auftritt.

Weiterlesen →

Das Debug-Level mit error_reporting setzen

Jeder von euch kennt vermutlich das Problem. Ihr arbeitet an einer Website und es gibt Fehler. Manchmal ist es sogar ein kritischer und ihr seht das hier:

Aber in manchen Fällen tritt der Fehler nicht in jedem Request auf. Dann solltet ihr den Debug-Modus aktivieren und alle Fehler protokollieren lassen.

Den Debug-Modus aktivieren

Um den Debug-Modus von WordPress zu aktivieren, müsst ihr lediglich ein paar Konstanten in der wp-config.php setzen. Das hier sind die Standardwerte:

Weiterlesen →

Das Löschen von eingebundenen Post-Type-Einträgen verhindern

Dies ist sicher ein Problem, in das einige von euch auch bereits gerannt sind. Ihr habt einen Post-Type-Eintrag in eine Seite oder einen Beitrag eingefügt und diesen dann später gelöscht, ohne zu wissen, dass dieser noch irgendwo eingebunden war. In diesen Beitrag will ich am beliebten Kontakt-Formular-Plugin „Contact Form 7“ demonstrieren, wie das verhindert werden kann. Es funktioniert aber natürlich auch mit jedem anderen Inhaltstyp.

Vorbereitung

Wir installieren das Contact Form 7 Plugin, erstellen ein Formular und fügen dieses in eine Seite mit dem mitgelieferten Gutenberg Block ein. Der Inhalt der Seite sieht dann in etwa wie folgt aus:

<!-- wp:contact-form-7/contact-form-selector {"id":39,"title":"Contact form 1"} -->
<div class="wp-block-contact-form-7-contact-form-selector">[contact-form-7 id="39" title="Contact form 1"]</div>
<!-- /wp:contact-form-7/contact-form-selector -->

Wir sehen hier die ID des Post-Types wpcf7_contact_form, die einmal in den Block-Attributen und einmal im inneren Shortcode verwendet wird, den das Plugin noch immer verwendet.

Löschen eines Post-Type-Eintrags

Wenn wir nun zu „Formulare -> Kontaktformulare“ navigieren können wir über die „Mehrfachaktionen“ das Kontaktformular löschen. Öffnen wir im Anschluss die Seite, auf der wir das Formular eingebunden haben im Frontend, erhalten wir folgendes, ziemlich witzige, Resultat:

[contact-form-7 404 "Nicht gefunden"]

Anstelle des Kontakformulars wird also ein „defekter Shortcode“ angezeigt. Für andere Post-Types wird die „Fehlerbehandlung“ eventuell anders aussehen oder im schlimmsten falsche eure Seite zerschießen.

Post-Type-Einträge vor einer Löschung schützen

Wie können wir also nun vermeiden, dass ein Eintrag gelöscht wird, der noch wo anders eingebunden ist? Nun, hierzu müssten wir alle andern Post-Types nach einem solchen Eintrag durchsuchen. Wenn wir hierzu den „Post-Content“ nach Markup durchsuchen, wird das eine recht teure Operation sein. Für manche Einbindungen wird es wohl auch nicht so einfach funktionieren. Wir holen uns daher ein wenig Hilfe, um die Operation schneller und zuverlässiger zu machen.

Verwendung einer „Hilfstaxonomy“

Wir führen eine Taxonomie ein, die uns beim Verhindern einer Löschung hilft. Diese Taxonomie benötigt kein UI und muss auch nicht öffentlich sein. Daher reichen wenige Zeilen aus, um sie zu registrieren:

function deletion_prevention_register_taxonomy() {
	register_taxonomy(
		'deletion_prevention_tax',
		array( 'post' ),
		array(
			'hierarchical' => false,
			'public'       => false,
		)
	);
}
add_action( 'init', 'deletion_prevention_register_taxonomy' );

Wie müssen sie auch nicht für jeden Post-Type registrieren, mit dem wir sei verwenden wollen, die Registrierung für Beiträge reicht aus. Nun können wir diese Taxonomie hinzufügen.

Hilfstaxonomie zu Seiten und Beiträgen hinzufügen

Für unser Beispiel parsen wir jedes Mal, wenn ein Post-Type gespeichert wird alle Blöcke. Finden wir hierbei einen Kontaktformular Block, dann verwenden wir dessen ID als der Namen des Terms:

function deletion_prevention_save_post( $post_ID, $post, $update ) {
	if ( ! in_array( $post->post_type, array( 'post', 'page', true ) ) ) {
		return;
	}

	$blocks = parse_blocks( $post->post_content );

	$forms = array();
	foreach ( $blocks as $block ) {
		if ( 'contact-form-7/contact-form-selector' === $block['blockName'] ) {
			$forms[] = (string) $block['attrs']['id'];
		}
	}

	wp_set_object_terms( $post_ID, $forms, 'deletion_prevention_tax' );
}
add_action( 'save_post', 'deletion_prevention_save_post', 10, 3 );

In der Callback-Funktion limitieren wir die Überprüfung auf Beiträge und Seiten und suchen nach dem contact-form-7/contact-form-selector Block. Wenn ihr das Löschen mehrerer eingebundener Post-Types verhindern wollt, könnten ihr zusätzliche Taxonomien verwenden, einen anderen Namen für den Term oder Metadaten.

Indem wir nun diese Taxonomie speichern, können wir sehr einfach und genau feststellen, wo Kontaktformulare eingebunden wurden. Wollen wir ein solches Formular löschen, können wir dann diese Seiten und Beiträge sehr schnell finden.

Verhinderung der Löschung

Jedes Mal, wenn ein Post-Type-Eintrag gelöscht wird, werden eine Reihe von Actions ausgelöst. Eine davon wird kurz vor der eigentlichen Löschung ausgeführt und hier können wir uns einhängen:

function deletion_prevention_delete_check( $delete, $post, $force_delete ) {
	deletion_prevention_check( $post );
}
add_action( 'pre_delete_post', 'deletion_prevention_delete_check', 10, 3 );

Einige Inhaltstypen unterstützen das Verschieben von Einträgen in den Papierkorb (Contact Form 7 unterstützt dies nicht), es kann also hilfreich sein, auch dies abzufangen:

function deletion_prevention_trash_check( $trash, $post ) {
	deletion_prevention_check( $post );
}
add_action( 'pre_trash_post', 'deletion_prevention_trash_check', 10, 2 );

Jetzt seht ihr auch, weshalb ich eine weiter Funktion für die Überprüfung verwendet habe.

Überprüfung auch aktuelle Einbindungen eines Eintrags

Jetzt sind wir im letzten Schritt. Wir suchen mit Hilfe einer Taxonomy-Query nach Einbettungen des zu löschenden Eintrags in Seiten und Beiträgen, indem wir dessen ID als Term-Slug verwenden:

function deletion_prevention_check( $post ) {
	$args = array(
		'post_type'      => array( 'post', 'page' ),
		'post_status'    => array(
			'publish',
			'pending',
			'draft',
			'future',
			'private',
		),
		'tax_query'      => array(
			array(
				'taxonomy' => 'deletion_prevention_tax',
				'field'    => 'slug',
				'terms'    => (string) $post->ID,
			),
		),
		'posts_per_page' => 1,
		'fields'         => 'ids',

	);
	$posts = get_posts( $args );

	if ( ! empty( $posts ) ) {
		wp_die(
			wp_kses_post(
				sprintf(
					__(
						/* translators: %1$s: link to a filtered posts list, %2$s: link to a filtered pages list */
						'This item as it is still used in <a href="%1$s">posts</a> or <a href="%2$s">pages</a>.',
						'deletion-prevention'
					),
					admin_url( 'edit.php?post_type=post&amp;taxonomy=deletion_prevention_tax&amp;term=' . $post->ID ),
					admin_url( 'edit.php?taxonomy=deletion_prevention_tax&amp;term=' . $post->ID )
				)
			)
		);
	}
}

Wenn diese Query mindestens einen Beitrag oder eine Seite zurück liefert, dann wissen wird, dass der Post-Type-Eintrag noch immer verwendet wird. In diesem Fall unterbrechen wir den aktuellen Request mit einem wp_die, was auch die Löschung unterbindet. In der Nachricht sind zwei Links enthalten, die auf die Listen von Beiträgen bzw. Seiten verweisen, in denen der Eintrag eingebunden ist. Hiermit kann man ihn dann schnell dort entfernen und anschließend gefahrlos löschen.

Fazit

Es passiert sehr leicht, dass man aus Versehen einen Eintrag eines Post-Types löscht, der noch an anderer Stelle verwendet wird. Im Core gibt es leider keine Möglichkeit, so etwas einfach zu verhindern. Durch unsere Hilfstaxonomie können wir dies nun sehr einfach absichern. Der gleiche Ansatz würde auch funktionieren, um das Ändern des Status eines Eintrags in einen „nicht öffentlichen“ Status zu verhindern. Das Löschen von Medieninhalten lässt sich hiermit leider nicht direkt abfangen (obwohl diese auch ein Post-Type sind). Ich hoffe dieser letzte Beitrag für 2020 hilft euch ebenfalls in einem Projekt weiter. Den Quellcode dieses Beitrags findet ihr als Plugin in einem GIST, welches ihr gerne ausprobieren oder für eure Anforderungen anpassen könnt.

Strings zur Übersetzung aus Gutenberg-Blöcken extrahieren

Während der Entwicklung meiner ersten komplexeren Blöcke bin ich in verschiedene Probleme gelaufen. Eines davon war das Extrahieren meiner Strings für Übersetzungen. In meinem vorherigen Beitrag habe ich beschrieben, wie man solche Strings aus PHP-Dateien extrahieren kann. Diese Methoden funktionieren grundsätzlich auch für JavaScript-Dateien, da sie „die gleichen Funktionen“ verwenden, bzw. Funktionen mit einem gleichlautenden Alias.

Strings in einem Gutenberg-Block übersetzen

Schauen wir uns zuerst einmal an wie Strings in einem Block übersetzt werden. Dieses Beispiel stammt aus dem Create a Block Tutorial und zeigt die „Edit“ Komponente:

import { TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
 
export default function Edit( { attributes, className, setAttributes } ) {
    return (
        <div className={ className }>
            <TextControl
                label={ __( 'Message', 'gutenpride' ) }
                value={ attributes.message }
                onChange={ ( val ) => setAttributes( { message: val } ) }
            />
        </div>
    );
}

Die __ Funktion wird von der @wordpress/i18n Komponente importiert. Diese Code funktioniert problemlos, um den String in Zeile 8 zu übersetzen. Es schlägt nur fehl, wenn man versucht den String in eine PO-Datei zu exportieren.

Kompilierte JavaScript-Datei

Aber wieso funktioniert das das Beispiel nicht, wenn man versucht die Strings zu extrahieren? Wenn man mit Komponenten wie diesen arbeitet, dann verteilt man den JavaScript Code in der Regel auf mehrere Dateien. Diese importiert dann man dann in eine Haupt-Datei und kompiliert diese in eine einzelne Datei, die an den Browser ausgeliefert wird. Hierzu verwendet man zum Beispiel @wordpress/scripts, das auch im Kapitel the JavaScript Build Setup beschrieben wird. Dieses Skript erstellt dann eine Datei build/index.js und die Strings aus dem Beispiel sehen dann wie folgt aus:

// ...
/* harmony import */ var _wordpress_i18n__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @wordpress/i18n */ "@wordpress/i18n");
// ...
function Edit(_ref) {
  var attributes = _ref.attributes,
      className = _ref.className,
      setAttributes = _ref.setAttributes;
  return Object(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__["createElement"])("div", {
    className: className
  }, Object(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__["createElement"])(_wordpress_components__WEBPACK_IMPORTED_MODULE_1__["TextControl"], {
    label: Object(_wordpress_i18n__WEBPACK_IMPORTED_MODULE_2__["__"])('Message', 'block-i18n'),
    value: attributes.message,
    onChange: function onChange(val) {
      return setAttributes({
        message: val
      });
    }
  }));
}

In Zeile 11 finden wir wieder die Strings. Allerdings sind sie nicht direkt von der __ Funktion umschlossen. Stattdessen wurden beim Kompilieren sehr merkwürdige Funktionsnamen generiert, die beim Import durch Webpack entstanden sind. Da diese Syntax dem wp i18n make-pot Kommande oder der X-Poedit-KeywordsList in Poedit bekannt ist, können sie beim Parsen nicht gefunden werden.

Lösung: Eine andere Syntax beim Importieren von Komponenten verwenden

Es gibt eine sehr einfache Lösung für das Problem. Man kann Komponenten auf verschiedene Weisen importieren. Wir ersetzen den Import der i18n Funktion in Zeile 2 einfach durch diesen „Import“:

const { __ } = wp.i18n;

Als Ergebnis erhalten wir dann die folgende kompilierte JavaScript-Datei, welche wieder direkt die __ Funktion verwendet (in Zeile 10), die der Parser dann wieder erkennt:

// ...
var __ = wp.i18n.__;
function Edit(_ref) {
  var attributes = _ref.attributes,
      className = _ref.className,
      setAttributes = _ref.setAttributes;
  return Object(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__["createElement"])("div", {
    className: className
  }, Object(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__["createElement"])(_wordpress_components__WEBPACK_IMPORTED_MODULE_1__["TextControl"], {
    label: __('Message', 'block-i18n'),
    value: attributes.message,
    onChange: function onChange(val) {
      return setAttributes({
        message: val
      });
    }
  }));
}

Interessanterweise hat es auch dann funktioniert, wenn ich den import entfernt und nicht ersetzt habe. Eventuell wurde die __ Funktion bereits durch eine andere Komponente importiert und war deshalb verfügbar. Ich würde dennoch empfehlen alle externen Komponenten und Funktionen, die ihr in eurem Code verwenden auch explizit zu importieren. Auf die gleiche Weise könnte man übrigens auch die erste Zeile umschreiben:

const { TextControl } = wp.components;

Andere Tutorials verwenden immer diese Methode um Komponenten von WordPress/Gutenberg zu importieren und ich würde euch auch entfernen, es immer so zu tun. Ich hatte bisher damit noch keine Probleme und die resultierende JavaScript-Datei war sogar ein wenig kleiner.

Fazit

Das Übersetzen von String in Gutenberg-Blöcken (oder ähnlichem JavaScript-Code für WordPress) ist wirklich sehr einfach und man kann die gleichen Funktionen verwenden, die man auch von PHP her kennt. Wenn man aber die Imports „falsch“ machen, wird das Extrahieren von Strings mit Poedit nicht funktionieren.

Da es aber Plugins gibt, die dennoch Imports auf diese Weise machen und dennoch funktionsfähige Übersetzungen habe, gibt es vielleicht doch einen Weg. Wenn ihr vielleicht wisst, wie diese Plugins es machen, würde ich mich sehr freuen, wenn ihr es mir vielleicht in einem Kommentar erklärt.

Ein Plugin ohne zusätzlich Software oder Plugins übersetzen

Wenn ihr ein Plugin oder Theme übersetzen möchtet, dann würde ich euch immer empfehlen dies mit der kostenlosen Software Poedit zu machen, die es für Windows, Linux und Mac gibt. Die Pro-Version bringt ein paar spezielle WordPress-Funtionalitäten mit, aber die sind nicht unbedingt notwendig. Dennoch kann ich diese absolut empfehlen. Für einen einmaligen Kaufpreis ist die Software recht günstig und sie kann euch viel Zeit sparen, besonders bei der Übersetzung von Themes, die sehr ähnliche Strings haben.

Aber in diesem Beitrag soll es nicht darum gehen, wie ihr mit Poedit Plugins oder Themes übersetzt. Stattdessen will ich euch zeigen, wie ihr das nur mit ein paar Befehlen auf der Kommandozeile umsetzen könnt. Also auch wenn ihr keine zusätzlich Software (oder Plugins) verwenden könnt, ist es möglich.

Erstellen eines Übersetzungs-Templates

Der erste Schritt ist die Erstellung einer POT (Portable Object Template) Datei mit allen Strings, die euer Plugin/Theme verwendet. Wenn ich Poedit verwende, dann überspringe ich diesen Schritt meistens und verwende eine sprachspezifische Vorlage, die es Poedit erlaubt nach übersetzbaren Strings zu suchen. Der einfachste Weg ist aber die Verwendung eines WP-CLI Befehls um eine solche Datei zu erstellen:

wp i18n make-pot . languages/text-domain.pot

Wenn ihr diesen Befehl ausführt werden alle PHP und JavaScrupt Datein nach Strings durchsucht, deren Text-Domain gleich dem Namen des Plugin/Theme-Ordner ist. Es erstellt eine POT Datei im languages Ordner (ihr müsst einen solchen Ordner eventuell vorher erstellen). Dieser Befehl kennt alle Übersetzungsfunktionen von WordPress (die gleichen Funktionen, die ich in meiner Vorlage stehen habe).

Erstellen einer sprachspezifischen Übersetzung

Ale nächstes erstellen wir eine Übersetzung für eine Sprache, indem wir die POT Datei in eine PO (Portable Object) Datei kopieren. Hierzu wird die „Locale“ für die Sprache angehängt:

cp languages/text-domain.pot languages/text-domain-de_DE.po

Nun könnt ihr diese Datei in einem Texteditor öffnen und die Strings manuell übersetzen. Im Header der Datei könnt ihr noch die Sprache festlegen, technisch ist es aber nicht unbedingt notwendig.

Erstellen der binären Übersetzungsdatei

WordPress verwendet MO (Machine Object) Dateien, um Übersetzungen zu laden. Auch hierzu gibt es einen Befehl, der in vielen Betriebssystemen verfügbar ist (oder unter Windows in der Git-Bash, cygwin oder ähnlichem enthalten ist):

msgfmt languages/text-domain-de_DE.po -o languages/text-domain-de_DE.mo

Mehr braucht es nicht, um Übersetzungen zu erstellen. Ihr könnt nun überprüfen, ob die Übersetzungen in eurer WordPress-Installation verwendet werden (nachdem ihr natürlich sichergestellt habt, dass ihr eine entsprechende load_textdomain in eurem Plugin/Theme habt, der die Übersetzungsdatei lädt).

Bonus: Übersetzungen aktualisieren

Jedes Mal, wenn ihr Änderungen an den Strings vornehmt, müsst ihr die Übersetzungsdateien aktualisieren. Wenn ihr Poedit verwendet (und es richtig konfiguriert habt), könnt einfach nach diesen neuen Strings suchen lassen. Wenn ihr aber nur das Terminal verwendet ist die ebenfalls möglich. Führt dazu folgenden erneut Befehl aus:

wp i18n make-pot . languages/text-domain.pot

Dies generiert jedes mal eine neue Vorlagendatei und überschreibt die bestehende. Da POT Dateien keine übersetzen Stings enthalten (da sie nicht für eine spezifische Sprache verwendet werden) ist das kein Problem. Aber wie aktualisiert ihr eine sprachspezifische Übersetzungsdatei, ohne alle Strings erneut zu übersetzen? Hierzu könnt ihr das merge Argument verwenden:

wp i18n make-pot . languages/text-domain-de_DE.po --merge=languages/text-domain-de_DE.po

Das scannt erneut alle PHP und JavaScript Dateien aus dem aktuellen Ordner und „merged“ es mit der bestehenden Sprachdatei und schreibt alles in diese zurück. Nur Strings, die noch nicht in diese Datei vorhanden sind, werden neu hinzugefügt. Bereits übersetzte Strings bleiben erhalten. Der einzige Nachteil: der Befehl entfernt keine Strings, die nicht mehr in euren PHP oder JavaScript Dateien vorhanden sind.

Fazit

Wenn ihr sehr einfach Übersetzungen von Plugins oder Themes anfertigen wollt, dann verwendet am besten Poedit oder ähnliche Lösungen. Aber in den Fällen, in denen ihr diese nicht verwenden könnt, habt ihr auch über das Terminal die Möglichkeit zu übersetzen.

Ein Gebiet, bei dem der wp i18n make-pot Befehl sehr nützlich sein kann ist der Einsatz in eurem CI/CD-Workflow. Hier erstellt ihr einen Task, der diesen Befehl ausführt und immer eine aktuelle POT Datei zu euren aktuellen Entwicklungsstand erstellt.

Blöcke für bestimme Post-Types deaktivieren

In dem Projekt, in dem ich WordPress als Backend für eine native App einsetze, habe ich zwei Custom-Post-Type implementiert. Für beide soll nur eine eingeschränkte Auswahl an Blöcken verfügbar sein. Das hat einfach damit zu tun, dass nicht alle Arten von Inhalten in einer nativen App angezeigt/verwendet werden können.

Erlaubt Blöcke filtern

Wir können den Filter allowed_block_types nutzen, um die verwendbaren Blöcke einzuschränken oder um alle Blöcke zu erlauben/verbieten. Wenn ihr also für einen spezifischen Post-Type erlauben möchtet, könnt ihr den Filter wie folgt verwenden:

function cpt_allowed_blocks( $allowed_block_types, $post ) {
	if ( 'my_post_type' === $post->post_type ) {
		return array(
			'core/paragraph',
			'core/list',
			'core/quote',
			'core/code',
			'core/table',
			'core/image',
			'core/video',
			'core/audio',
		);
	}

	return $allowed_block_types;

}
add_action( 'allowed_block_types', 'cpt_allowed_blocks', 10, 2 );

In der Callback-Funktion überprüfen wir zuerst, ob gerade unser Custom-Post-Type bearbeitet wird. Wenn dem so ist, dann liefern wir ein Array mit genau den Blöcken zurück, die verwendet werden dürfen. Ansonsten liefern wir Originalwert unverändert zurück.

Der Originalwert kann entweder ein Array oder ein boolscher Wert sein. Wenn hier true zurück geliefert wird, sind alle Blöcke erlaubt, bei false sind keine Blöcke erlaubt.

Bonus: Ein Block-Template für den Post-Type setzen

Wenn ihr die Auswahl der Blöcke einschränkt, dann möchtet ihr damit vermutlich ein bestimmtes Layout in eurem Post-Type erreichen. Ihr könnt das vereinfachen, indem ich ein paar Blöcke standardmäßig hinzufügt, wenn ein neuer Eintrag im Post-Type erstellt wird. Das Template wird in der Funktion zur Registrierung eines Post-Types angegeben:

function cpt_register_post_type() {
	register_post_type(
		'my_post_type',
		array(
			'label'                 => __( 'My Post Type', 'text_domain' ),
			'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' => __( 'Lorem ...', 'text_domain' ),
					),
				),
				array(
					'core/image',
				),
			),
		)
	);
}
add_action( 'init', 'cpt_register_post_type' );

Mit dem template Parameter könnt ihr mehrere Blöcke definieren, die bei jedem neuen Eintrag eingefügt werden. Jeder Block wird in einem eigenen Array definiert. Für viele Blöcke können darin zusätzlich Eigenschaften gesetzt werden. In diesem Beispiel wird für den Absatz-Block ein Platzhaltertext (nicht der Inhalt) gesetzt. Der zweite Block ist ein Bild-Block, ohne Eigenschaften. Er sieht genau so aus, wie direkt nach dem Hinzufügen der Block, er befindet sich also im „Bearbeitungsmodus“:

Fazit

Wenn ihr mit Custom-Post-Types arbeitet, dann könnt ihr recht einfach einschränken, welche Blöcke verwendet werden dürfen. In Kombination mit einem Block-Template für neue Einträge könnt ihr die Erstellung von einheitlichen Einträgen erheblich vereinfachen.

Ändern der Standardwerte für Absender-Name und E-Mail-Adresse

In WordPress werden an vielen verschiedenen Orten E-Mails versendet. Meistens werden sie von Plugins versendet. In den meisten davon kann man den Absender-Name („From Name“) und die E-Mail-Adresse („From Mail“) selbst festlegen. Einige E-Mails werden aber auch vom WordPress Core verwendet. Eine solche E-Mail wäre etwa die zum Zurücksetzen des Passworts. Aber auch andere Plugins schicken Mails an Nutzer oder Administratoren.

Ändern der Absender-Standardwerte

Wenn eine E-Mail versendet wird und dabei der Name und die Adresse nicht explizit gesetzt werden, dann wird immer „WordPress“ für den Namen und „wordpress@deine-domain.tld“ verwendet. Leider gibt es im Dashboard keine Einstellung um diese Werte zu ändern.

Überschreiben der Werte mit einem Filter

Es gibt zwei Filter, mit denen die Standardwerte für den Absendernamen und die E-Mail-Adresse geändert werden können. Fügt einfach folgendes in ein (MU-)Plugin ein und aktiviert es:

// Change the "from name"
function wp_email_set_from_name( $from_name ) {
	return 'Dein Name';
}
add_filter( 'wp_mail_from_name', 'wp_email_set_from_name' );
// Change the "from email"
function wp_email_set_from_email( $from_email ) {
	return 'dein-name@deine-domain.tld';
}
add_filter( 'wp_mail_from', 'wp_email_set_from_email' );

Das überschreibt die beiden Werte. Aber es überschreibt sie für alle E-Mails. Vermutlich nicht das, was ihr wollt. Leider werden die beiden Filter erst dann aufgerufen, wenn die Standardwerte schon gesetzt wurden. Man kann also nicht einfach prüfen, ob explizite Werte zum Senden der E-Mail verwendet wurden. Daher prüfen wir stattdessen, ob die Werte noch immer der Standardwerten entsprechen und ändern sie nur in diesem Fall:

// Change the default "from name"
function wp_email_set_from_name( $from_name ) {
	if ( 'WordPress' === $from_name ) {
		return 'Dein Name';
	}

	return $from_name;
}
add_filter( 'wp_mail_from_name', 'wp_email_set_from_name' );
// Change the default "from email"
function wp_email_set_from_email( $from_email ) {
	if ( false !== strpos( $from_email, 'wordpress@' ) ) {
		return 'dein-name@deine-domain.tld';
	}

	return $from_email;
}
add_filter( 'wp_mail_from', 'wp_email_set_from_email' );

Dies sollte also nur dann neue Werte setzen, wenn sie noch immer den Standardwerten entsprechen. Ihr könnt aber weiterhin einen anderen Namen oder eine andere E-Mail-Adressen, zum Beispiel in einem Formular-Plugin.

Fazit

Das Überschreiben der Standardwerte für den Absender-Namen und die E-Mail-Adresse kann mit Filtern umgesetzt werden. Ihr solltet dabei aber aufpassen, dass ihr es nur in den Fällen tut, in denen die Werte nicht zuvor auf einen anderen Wert schon gesetzt wurden.

Benutzer in WordPress mit der REST API erstellen

In einem aktuellen Projekt verwenden wir WordPress als Backend für eine native Mobile App. Die App selbst wird von einer anderen Agentur entwickelt. Ich bin für den WordPress-Teil zuständig. Der Inhalt der App wird über mehrere Custom-Post-Types und Custom-Taxonomies sowie einiger spezieller Blocks erstellt, für eine komfortable Verwaltung. Die App kann ohne Registrierung verwendet werden. Für erweiterte Funktionen, wie etwas die Synchronisation der Ergebnisse mit mehreren Geräten oder der Vergleich mit anderen, wird eine Registrierung benötigt.

Der Server für den Empfang von Requests vorbereiten

Um Anfragen von der App an den Server machen zu können, müssen einige Response-Header geschickt werden, damit diese akzeptiert werden. Die gilt besonders für die iOS-Version der App. Ich habe zuerst versucht die Header über die Server-Konfiguration umzusetzen, nur um dann festzustellen, dass einige davon zusätzlich noch von WordPress gesetzt wurden. Das Resultat waren dann doppelte Header mit gleichen Namen aber unterschiedlichen Werten. Nach ein wenig Debugging konnte ich ein paar Hooks finden, die ich verwenden konnte:

function app_rest_headers_allowed_cors_headers( $allow_headers ) {
	return array(
		'Origin',
		'Content-Type',
		'X-Auth-Token',
		'Accept',
		'Authorization',
		'X-Request-With',
		'Access-Control-Request-Method',
		'Access-Control-Request-Headers',
	);
}
add_filter( 'rest_allowed_cors_headers', 'app_rest_headers_allowed_cors_headers' );

Bei einem anderen Hook gab es leider keinen Filter, daher musste ich hier direkt die header() Funktion aufrufen:

function app_rest_headers_pre_serve_request( $value ) {
	header( 'Access-Control-Allow-Origin: *', true );

	return $value;
}
add_filter( 'rest_pre_serve_request', 'app_rest_headers_pre_serve_request', 11 );

Nach dieser kleinen Vorbereitung können wir versuchen über den Users-Endpoint zu erstellen. Das wird aber nicht funktionieren. Wieso nicht? Nun, ganz einfach aus dem Grund, dass man nicht einfach über die WordPress REST API einen Benutzer erstellen kann, ohne sich vorher zu autorisieren. Wenn das möglich wäre könnte ja sonst jemand auf einer beliebigen WordPress-Seite Benutzer erstellen.

Einen Nutzer mit der REST API authentifizieren

Wie müssen uns also an der REST API authentifizieren, aber wie machen wir das? Es gibt hier verschiedene Möglichkeiten. Eine neue Möglichkeit, die als erstes im Core langen wird und kurz vor der Aufnahme steht sind die Application Password.

Es gibt schon einen Vorschlag für die Integration des Feature-Projects in WordPress 5.6. Wenn ihr aber jetzt schon diese Möglichkeit testen wollt, könnt ihr einfach das Plugin Application Passwords installieren.

Einen Benutzer für die Benutzerverwaltung erstellen

Normalerweise können nur Administratoren die Benutzer verwalten. Aber ihr möchtet vermutlich nicht unbedingt einer App sämtliche Berechtigungen geben, die ein Admin auf einer WordPress-Seite hat. Daher macht es Sinn, einen speziellen Benutzer zu erstellen, der nur die Berechtigungen hat, die für die Benutzerverwaltung benötigt werden. Ihr könnt das mit einem Plugin wie etwa Members machen, oder aber ihr erstellt die Rolle und den Benutzer mit PHP Code oder ihr verwendet die WP-CLI:

wp role create app App --clone=subscriber
wp cap add app create_users
wp user create app-rest-user app-rest-user@example.com --role=app

Der Benutzer braucht mindestens die Berechtigung create_users, um Benutzer anlegen zu können. Wenn ihr zusätzlich die read Berechtigung setzt, könnt ihr euch auch mit diesem Benutzer anmelden und das Application Password setzen (daher wird in dem Beispiel oben auch die Rolle subscriber geklont). Alternativ könnt ihr als Admin das Application Password für einen Benutzer setzen.

Das Application Passwort für den neuen Benutzer erstellen

Nachdem ihr den Nutzer erstellt habt, könnr ihr euch mit diesem einloggen und auf die Profileinstellungen gehen (oder eben das Profil des Nutzers mit dem Admin bearbeiten). Hier findet ihr ein neues Feld, in dem ihr Application Passwords erstellen könnt. Wählt hier einen Namen aus und klickt auf „Add new“ (zum Zeitpunkt der Veröffentlichung des Beitrags gab es noch keine Übersetzung für das Plugin bzw. die Core-Funktionalität):

Daher öffnet sich ein Modal mit dem generierten Application Password. Ihr müsst dieses Passwort hier kopieren, da es nach dem Schließen des Modal nicht mehr als Klartext angezeigt werden kann.

Damit ist jetzt alles vorbereitet um einen Benutzer über einen Request an die REST API zu erstellen.

Deinen ersten Benutzer erstellen

Je nachdem welches System ihr verwendet, um Requests an die WordPress REST API zu schicken, sieht der Workflow hier anders aus. Daher zeige ich es heir am Beispiel eines curl Requests über die Kommandozeile:

curl --user "app-rest-user:younfj4FX6nnDGuv9EwRkDrK" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"username":"jane","password":"secret","email":"jane@example.com"}' \
     http://example.com/wp-json/wp/v2/users

Wenn ihr zuvor alles richtig eingerichtet habt, dann solltet ihr eine JSON-Response mit den Daten zum neuen Benutzer erhalten.

Fazit

Die Verwaltung von Benutzern ist nach etwas Vorbereitung und (aktuell) noch externer Plugins möglich. Es wird durch das neue Applications Passwords Core-Feature aber um einiges leichter. In gleicher Weise könnt ihr dann natürlich auch andere REST API Requests machen, die einen autorisierten Benutzer erfordern. Für eine erhöhte Sicherheit solltet ihr hierzu aber immer einen speziellen Benutzer erstellen (es sei denn, ihr wollte Inhalte für einen bestehenden Benutzer erstellen und zuweisen).

Disclaimer

Seit fast genau vier Jahren versuche ich all meine Beiträge geschlechtsneutral zu schreiben. Das ist mir bisher auch recht gut gelungen (auch wenn ich sicher doch mal aus Versehen etwas übersehen habe).

Im Englischen ist es bei Personenbezeichnungen auch recht einfach, da es hier nur selten eine männliche und weibliche Form gibt. In der deutschen Übersetzung verwende ich einfach statt einer Personenbezeichnung einen andren Begriff. Dadurch ist der Satz meisten genauso einfach lesbar, manchmal sogar besser. Aber beim heutigen Beitrag habe ich mich aber für „Benutzer“ und „Benutzerverwaltung“ entschieden. Ich hätte zwar auch „Zugang“ und „Zugangsverwaltung“ verwenden können, aber damit hätte ich wohl einige von euch sehr verwirrt. Der Terminus im Core ist aktuell eben noch „Benutzer“ und daher musste ich heute mal diese Ausnahme machen. Ich hoffe meine treuen Leserinnen verzeihen es mir 🙂