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&taxonomy=deletion_prevention_tax&term=' . $post->ID ),
					admin_url( 'edit.php?taxonomy=deletion_prevention_tax&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 🙂

Einzelne Block-Styles deaktivieren

In meinem letzten Beitrag habe ich euch gezeigt, wie man Seiten-Templates in einem Child-Theme deaktivieren kann. Diese Woche geht es um die Deaktivierung anderer Dinge, die über das Parent-Theme oder ein Plugin kommen können. Es geht um Block-Styles. Damit können verschiedene Stile für einen Block definiert werden, die entweder im Core, im Theme, aber manchmal auch in Plugins zu finden sind.

Deaktivierung über einen „Dequeue-Hook“

Block-Stile werden innerhalb von JavaScript-Dateien definiert. Diese Dateien müssen in die Seite eingebunden werden. Hier können wir ansetzen und die Datei wieder entfernen. Das könnte wie folgt aussehen:

function dibs_dequeue_assets() {
	wp_dequeue_script( 'editorskit-editor' );
}
add_action( 'enqueue_block_editor_assets', 'dibs_dequeue_assets', 11 );

In diesem Beispiel wird die Hauptdatei mit allen JavaScript-Funktionen für den Editor entfernt. Sie enthält also nicht nur Block-Styles, sondern den gesamten JavaScript-Code für das Plugin. Das ist also nur dann eine gute Option, wenn das Plugin mehrere Dateien verwendet und eine davon nur die Registrierung der Block-Styles übernimmt.

Deaktivierung ausgewählter Block-Stile

Wenn ihr also nur bestimmte Styles in backend deaktivieren möchtet, dann müsst ihr ein wenig eigenen JavaScript-Code verwenden. Hierzu laden wir zuerst die Datei mit diesem Code:

function dibs_enqueue_assets() {
	wp_enqueue_script(
		'dibs-remove-block-styles',
		plugins_url( 'remove-block-styles.js', __FILE__ ),
		array( 'wp-blocks', 'wp-dom-ready', 'wp-edit-post' ),
		filemtime( plugin_dir_path( __FILE__ ) . 'remove-block-styles.js' ),
		true
	);
}
add_action( 'enqueue_block_editor_assets', 'dibs_enqueue_assets', 11 );

Die hinzugefügt Datei lädt die Abhängigkeiten zu wp-blocks, wp-dom-ready und wp-edit-post um korrekt zu funktionieren. Nachdem die Datei hinzugefügt wurde, können wir die Block-Styles entfernen:

wp.domReady( function() {
	wp.blocks.unregisterBlockStyle(
		'core/image',
		'editorskit-diagonal'
	);
	wp.blocks.unregisterBlockStyle(
		'core/image',
		'editorskit-inverted-diagonal'
	);
	wp.blocks.unregisterBlockStyle(
		'core/cover',
		'editorskit-diagonal'
	);
	wp.blocks.unregisterBlockStyle(
		'core/cover',
		'editorskit-inverted-diagonal'
	);
} );

Nach dem Laden der Seite wird wp.blocks.unregisterBlockStyle() verwendet, deren erster Parameter den „Slug“ des Blocks angibt, für den wir einen Stil entfernen wollen. Der zweite Parameter ist der „Slug“ des Block-Style. In diesem Beispiel würden wir also zwei Block-Stile von den Blöcken core/image und core/cover entfernen.

Dieser Ansatz ist vermutlich derjenige, den ihr verwenden möchtet, da das Entfernen einer gesamten JavaScript-Datei wie im ersten Beispiel nur selten funktionieren wird. Aber auch, wenn ihr nur einzelne Stile entfernen möchtet, ist dies wohl die bessere Lösung.

Fazit

Falls ein Plugin neue Block-Styles mitbringt, die ihr nicht verwenden möchtet, dann reicht eine kleine JavaScript-Datei aus, um diese zu entfernen. Es ist aber zu beachten, dass dies nur die Auswahl dieser Stile im Backend entfernt. Bereits zuvor hinzugefügt Blöcke in Beiträgen und Seiten haben weiterhin den Stil in Form von CSS-Klassen. Sofern die CSS-Datei auch weiterhin die Anweisungen enthält, wird der Block-Stil weiterhin im Frontend angezeigt. Falls ihr das also ebenfalls deaktivieren wollt, müsst ihr entweder die CSS-Klassen von den alten Blöcken entfernen oder aber die CSS-Eigenschaften im (Child-)Theme entfernen oder überschreiben.

Seiten-Template im Child-Theme deaktivieren oder überschreiben

Vorletzte Woche hat ein Kollege an einem neuen Projekt gearbeitet, bei dem ein Seiten-Template im Parent-Theme defekt war. Es gab ein besseres und sehr ähnliches Template, das stattdessen verwendet werden konnte. Aber es besteht weiterhin die Gefahr, dass jemand das defekte Seiten-Template beim Erstellen einer Seite auswählen würde. Also habe ich mich mal auf die Suche nach einer Lösung begeben, mit der man ein Seiten-Template aus der Auswahl entfernen kann.

Deaktivieren eines Seiten-Templates

In einem Child-Theme kann man ja sehr einfach ein neues Seiten-Template hinzufügen oder ein bestehendes überschreiben, indem man es aus dem Parent-Theme kopiert. Aber wie kann man ein bestehendes Template aus dem Parent-Theme aus der Auswahl entfernen. Glücklicherweise gibt es fast immer einen passenden Hook. Wenn ihr also ein Seiten-Template deaktivieren wollt, dann verwendet diesen Filter:

function child_theme_disable_page_template( $post_templates, $theme, $post, $post_type ) {
	unset( $post_templates['page-templates/tpl-hero-light.php'] );

	return $post_templates;
}
add_filter( 'theme_templates', 'child_theme_disable_page_template', 10, 4 );

Der Filter theme_templates bekommt ein Array aller Templates übergeben mit den relativen Pfaden als Schlüssel und den (übersetzten) Namen als Werten. Es könnte in etwa wie folgt aussehen:

$post_templates = array(
	'page-templates/tpl-fullscreen-light.php'  => 'Fullscreen, light header',
	'page-templates/tpl-fullscreen.php'        => 'Fullscreen',
	'page-templates/tpl-fullwidth-notitle.php' => 'Full Width, no page title',
	'page-templates/tpl-fullwidth.php'         => 'Full Width',
	'page-templates/tpl-hero-light.php'        => 'Hero, light header',
	'page-templates/tpl-hero.php'              => 'Hero',
);

Das Array enthält sowohl die Seiten-Templates aus dem Parent-Theme, als auch die aus dem Child-Theme. Falls beide ein Template mit dem gleichen Pfad haben, ist dieses nur einmal im Array enthalten.

Ersetzen eines Seiten-Templates

Jetzt wisst ihr also, wie ihr verhindern könnt, dass jemand ein Seiten-Template verwendet, das ihr nicht haben möchtet. Aber was ist mir den Seiten, bei denen das falsche Template bereits eingestellt ist? Nun, eine Möglichkeit wäre es, in der Datenbank das „postmeta“ mit dem Schlüssel _wp_page_template mit dem neuen Wert zu ersetzen. Aber vielleicht möchtet ihr den aktuellen Wert in der Datenbank intakt lassen. Dann könnt ihr einen von mehreren Filtern verwenden, um den Pfad zum Template zu überschreiben. Dies ist einer der Filter, den ihr nutzen könnt:

function child_theme_overwrite_page_template( $template ) {
	if ( get_parent_theme_file_path( '/page-templates/tpl-hero-light.php' ) === $template ) {
		return get_theme_file_path( '/page-templates/tpl-hero.php' );
	}

	return $template;
}

add_filter( 'template_include', 'child_theme_overwrite_page_template' );

In diesem Beispiel würde also jede Seite, die das Template page-templates/tpl-hero-light.php aus dem Parent-Theme verwenden würde, stattdessen das Template page-templates/tpl-hero.php aus dem Child-Theme verwenden.

Fazit

Während es sehr einfach ist ein Seiten-Template in einem Child-Theme hinzuzufügen oder zu überschreiben ist es nicht so einfach eines zu deaktivieren. Aber da WordPress normalerweise für jede gewünschte Anpassung einen passenden Filter anbietet, ist es auch nicht wirklich schwer.

Plugins und Themes mit WordPress 5.5 aktualisieren oder zurücksetzen

Die aktuelle WordPress Hauptversion 5.5 wurde Mitte August veröffentlicht. Es gab viele schöne neue Funktionen. Aber eine davon wurde nur in wenigen Beiträgen zur neuen Version erwähnt. Die Möglichkeit ein installiertes Plugin oder Theme durch das Hochladen einer ZIP-Datei zu ersetzen.

Einige bisherige Möglichkeiten, um installierte Plugins oder Themes zu ersetzen

In WordPress ist es wirklich sehr einfach eine Plugin oder Theme auf die neueste Version zu aktualisieren (zumindest für solche, die auf WordPress.org verfügbar sind). Das Zurücksetzen auf eine ältere Version oder das aktualisieren einer Premium-Version ist nicht ganz so einfach.

Mit der WP-CLI

Falls du die WP-CLI verwendest, kannst du den install Befehle für Plugins oder Themes verwenden und dabei eine spezifische Version installieren. Falls das Plugin oder Theme bereits installiert ist, kann man die Installation erzwingen:

wp plugin install gutenberg --version=8.9.0 --force

Dies überschreibt die aktuell installierte Version mit der angegebenen Version. Möglich ist das aber nur für Plugins und Themes aus den WordPress.org Verzeichnissen.

Hochladen einer alten Version mit SFTP/FTPS/FTP

Wenn du keinen SSH-Zugang zum Server hast oder dort die WP-CLI nicht verwenden kannst, dann ist die Übertragung mit einem FTP-Programm eine Alternative. Hierzu löscht man zuerst die aktuelle Version und ersetzt sie dann mit der alten Version. Dies hat aber den Nachteil, dass während der Übertragung der Dateien, was je nach Größe eine Zeit lang dauern kann, die Seite eventuell Fehler produziert. Man kann diese Ausfallzeit ein wenig reduzieren, indem man die alte Version in einen anderen Ordner hochlädt, dann die aktuelle Version löscht und schließlich den zuvor hochgeladenen Ordner umbenennt. Aber diese Weg ist nicht wirklich ideal.

Deaktivieren und Deinstallieren der aktuellen Version

Manche von euch haben jetzt vielleicht die Idee, einfach die aktuelle Version zu deaktivieren und deinstallieren, um anschließend die alte Version zu installieren. Aber das ist sehr gefährlich. Viele Plugins löschen ihre Einstellungen, wenn sie deaktiviert/deinstalliert werden. Manche löschen sogar all ihre Daten. Dieser Weg sollte also vermieden werden.

Ersetzen der aktuellen Version mit der neuen WordPress 5.5 Funktion

Falls ihr eure Seite schon auf WordPress 5.5 aktualisiert habt, könnt ihr den neuen Weg versuchen. Hierzu müsst ihr einfach die alte Version als ZIP-Datei hochladen. Diese habt ihr entweder in einem Backup oder ihr ladet sie aus dem Plugin- oder Theme-Verzeichnis von WordPress.org runter.

Download einer alten Version of eines Plugins

Für Plugins ist das sehr einfach. Hierzu navigiert ihr auf die Plugin-Seite auf WordPress.org und klickt auf den Link „Erweiterte Ansicht“ auf der rechten Seite:

Auf der folgenden Seite scrollt ihr dann ganz ans Ende. Dort findet ihr ein Auswahl von ältere Versionen. Dass sind all die Versionen, die getagged/veröffentlicht wurden:

Wählt hier die gewünschte Version auf und klickt auf den Herunterladen-Button. Ihr erhaltet dann die ZIP-Datei für diese Version. Falls im Auswahlfeld keine älteren Versionen verfügbar sein, dann wurden leider keine explizit veröffentlicht.

Hochladen der alten Version und ersetzen der aktuellen Version

Jetzt könnt ihr die Version hochladen. Das funktioniert genau so wie bei der ersten Installation eines Plugins oder Themes. Navigiert also im Dashboard zu „Plugins | Installieren“ und klickt dort auf den „Plugin hochladen“ Button neben der Überschrift. Auf der nächsten Seite könnt ihr dann die zuvor heruntergeladene Datei auswählen und mit einen Klick auf „Jetzt installieren“ hochladen. Anschließend solltet ihr folgendes sehen:

Auf der Seite seht ihr eine Übersicht zur aktuell installierten und zur hochgeladen Version. Für beide wird euch angezeigt, welche Versionen von WordPress und PHP jeweils erforderlich sind. Wenn ihr fortfahren wollt, klickt einfach auf den „Ersetze bestehendes mit hochgeladenem“ Button.

Fazit

Manchmal sind es die kleinen Änderungen in einer neuen Version, die einen Großen Unterschied machen. Das Zurücksetzen eines Plugins oder Themes war bisher keine einfache Aufgabe. Wenn allerdings deine Seite nach einem Update nicht mehr funktioniert hat, gab es die Notwendigkeit ein Plugin oder Theme zurücksetzen zu können. Mit der neuen Funktion ist das nun sehr viel einfacher. Aber auch die Aktualisierung einer Premium-Version eines Plugins oder Themes, die sich nicht in den Update-Mechanisum von WordPress integrieren, wird hiermit sehr viel einfacher.

Beitragsnavigation nach Namen sortieren

Bei vielen Kundenprojekten gibt es die Notwendigkeit eigene Inhaltstypen zu hinzuzufügen. In der Regel erstelle ich diese über den wp scaffold post-type Befehl der WP-CLI. Das erstellt die notwendigen PHP Funktionen inkl. eines Arrays von Labels, die dann übersetzt werden können. In der einfachsten Form kann man einen eigenen Inhaltstyp wie folgt registrieren:

function optn_register_post_type() {
	register_post_type(
		'sponsor',
		array(
			'label'       => __( 'Sponsor', 'optn' ),
			'public'      => true,
			'has_archive' => true,
			'show_ui'     => true,
		)
	);
}
add_action( 'init', 'optn_register_post_type' );
Weiterlesen →