12k
All articles

Ein WordPress-Plugin von Grund auf schreiben

Erstellen Sie ein WordPress-Plugin von Grund auf mit Hooks, Aktivierungs- und Deaktivierungs-Hooks, Einstellungsseite, Sanitizing, Escaping und sicherem Speichern.

OpenReplay Team
OpenReplay Team
Ein WordPress-Plugin von Grund auf schreiben

Ein WordPress-Plugin ist im einfachsten Fall ein Ordner in wp-content/plugins, der eine einzige PHP-Datei enthält, die mit einem Kommentar-Header beginnt, der das Plugin benennt. Speichere diese Datei, und WordPress führt das Plugin auf der Plugins-Seite auf.

Wenn du bisher Snippets in die functions.php eines Themes eingefügt hast, kennst du den Haken bereits: Wechselst du das Theme, verschwindet der Code mit ihm. Verlagerst du diesen Code in ein Plugin, bleibt die Funktionalität einem Theme-Wechsel gegenüber unversehrt – und genau das ist der wesentliche Grund, warum das Verhalten einer Website in Plugins gehört und die Darstellung in Themes.

Dieser Artikel baut ein kleines Plugin von Anfang bis Ende auf: einen Website-Hinweis, der im Footer ausgegeben wird und über eine Einstellungsseite bearbeitet werden kann, die sicher speichert. Jeder Codeblock stellt einen gültigen Zustand des Plugins dar, du kannst also nach jedem Abschnitt aufhören und das Bisherige aktivieren.

Die wichtigsten Punkte

  • Ein Plugin ist ein Ordner in wp-content/plugins mit einer PHP-Datei, deren Kommentar-Header das Plugin benennt; Plugin Name ist das einzige zwingend erforderliche Header-Feld.
  • Plugin-Code tut nichts, solange du eine Funktion nicht mit add_action() oder add_filter() an einen Hook hängst.
  • Ein Filter-Callback muss den erhaltenen Wert zurückgeben; gibt er nichts zurück, bleibt der gefilterte Wert leer.
  • Eine sichere Einstellungsseite kombiniert vier Dinge: eine Berechtigungsprüfung (Capability Check), eine Nonce, Bereinigung (Sanitisierung) der Eingabe und Escaping bei der Ausgabe.
  • register_activation_hook() ist für Standardwerte zuständig, die Deaktivierung für temporäre Aufräumarbeiten, und das dauerhafte Löschen von Daten gehört zur Deinstallation.

So erstellst du eine WordPress-Plugin-Datei mit Header

Erstelle den Ordner wp-content/plugins/site-notice/ und darin die Datei site-notice.php. WordPress baut die Plugins-Seite auf, indem es die PHP-Dateien im Plugin-Ordner liest und diejenigen heraussucht, die mit einem Plugin-Header beginnen. Jede Datei mit einem Header zählt als eigenes Plugin – setze den Header also nur in eine einzige Datei.

<?php
/**
 * Plugin Name:       Site Notice
 * Description:       Shows a short notice in the site footer, editable from Settings.
 * Version:           1.0.0
 * Requires at least: 6.5
 * Requires PHP:      7.4
 * Author:            OpenReplay Team
 * License:           GPL-2.0-or-later
 * Text Domain:       site-notice
 */

Die Seite zu den Header-Anforderungen listet alle erkannten Felder auf, einschließlich Update URI und Requires Plugins. Die Felder, die du am häufigsten verwenden wirst:

FeldWas WordPress damit machtErforderlich
Plugin NameWird in der Plugin-Liste angezeigtJa
DescriptionWird unter dem Namen angezeigtNein
VersionWird angezeigt; steuert Update-VergleicheNein
Requires at least / Requires PHPVerhindert die Aktivierung in älteren UmgebungenNein
Author, License, Text DomainUrheberangabe, Lizenzierung, Übersetzungs-SlugNein

Speichere die Datei, und das Plugin erscheint auf der Plugins-Seite. Es lässt sich sauber aktivieren und tut nichts.

Was machen Activation- und Deactivation-Hooks?

register_activation_hook() führt deinen Callback genau einmal aus – in dem Moment, in dem jemand das Plugin einschaltet. Das macht ihn zum geeigneten Ort, um deine anfänglichen Optionswerte in die Datenbank zu schreiben. register_deactivation_hook() dient dazu, alles wieder zu verwerfen, was das Plugin nur während der Laufzeit brauchte – ein Cache ist das übliche Beispiel. Das endgültige Löschen von Daten, einschließlich Optionen und eigener Tabellen, ist hingegen Aufgabe der Deinstallation, denn oft schalten Nutzer ein Plugin nur ab, um es später wieder einzuschalten.

Füge unterhalb des Headers hinzu:

function orp_activate() {
	add_option( 'orp_notice_text', 'Welcome to the site.' );
}
register_activation_hook( __FILE__, 'orp_activate' );

function orp_deactivate() {
	delete_transient( 'orp_notice_cache' );
}
register_deactivation_hook( __FILE__, 'orp_deactivate' );

Das erste Argument, __FILE__, verweist auf die Hauptdatei des Plugins. Unser Plugin cached noch nichts, aber der Deaktivierungs-Callback zeigt das Prinzip: temporäre Artefakte aufräumen, die gespeicherte Option unberührt lassen. Das Präfix orp_ bei jedem Funktions- und Optionsnamen verhindert Kollisionen mit dem Core und anderen Plugins.

Warum läuft mein Plugin-Code nicht?

Code in einer Plugin-Datei läuft nicht von sich aus. WordPress führt eine Funktion erst aus, nachdem du sie an einen Hook gehängt hast; bis dahin bleibt die Datei untätig. add_action() erwartet einen Hook-Namen und ein Callable, dazu optional eine Priorität, die standardmäßig 10 ist.

function orp_print_notice() {
	$notice = get_option( 'orp_notice_text' );
	if ( $notice ) {
		echo '<p class="orp-notice">' . esc_html( $notice ) . '</p>';
	}
}
add_action( 'wp_footer', 'orp_print_notice' );

Wenn das Theme wp_footer auslöst, ruft WordPress jede daran gehängte Funktion auf, auch unsere. Lade eine beliebige Frontend-Seite, und der Hinweis erscheint oberhalb des schließenden body-Tags.

Einen Filter hinzufügen: Wert verändern und zurückgeben

Eine Action lässt deine Funktion an einer bestimmten Stelle im Lebenszyklus von WordPress laufen; ein Filter übergibt deiner Funktion einen Wert und erwartet den geänderten Wert zurück. Ein Filter-Callback muss einen Wert zurückgeben. Gibt er nichts zurück, liefert PHP null, WordPress trägt dieses null weiter, und was du gefiltert hast, verschwindet von der Website. Vergiss das return in einem body_class-Callback, und das body-Element verliert seine Klassen; vergiss es bei the_content, und jeder Beitrag wird leer gerendert.

function orp_body_class( $classes ) {
	if ( get_option( 'orp_notice_text' ) ) {
		$classes[] = 'orp-has-notice';
	}
	return $classes;
}
add_filter( 'body_class', 'orp_body_class' );

Diese Funktion erhält das Array der Body-Klassen, hängt eine Klasse an, wenn ein Hinweis gesetzt ist, und gibt das Array zurück, damit Themes Seiten unterschiedlich gestalten können, solange der Hinweis aktiv ist.

Wie baut man eine WordPress-Einstellungsseite?

Eine Einstellungsseite besteht aus drei Registrierungen: einer Menüseite am Hook admin_menu sowie einer Einstellung mitsamt Section und Feld am Hook admin_init, so wie es das Kapitel zur Settings API darlegt. add_options_page() platziert die Seite unter „Einstellungen“; register_setting() benennt die Option und – entscheidend – ihren sanitize_callback.

function orp_settings_menu() {
	add_options_page( 'Site Notice', 'Site Notice', 'manage_options',
		'orp-site-notice', 'orp_settings_page_html' );
}
add_action( 'admin_menu', 'orp_settings_menu' );

function orp_settings_init() {
	register_setting( 'orp_settings', 'orp_notice_text', array(
		'type'              => 'string',
		'sanitize_callback' => 'sanitize_text_field',
	) );
	add_settings_section( 'orp_main', 'Notice', '__return_false', 'orp-site-notice' );
	add_settings_field( 'orp_notice_text', 'Notice text',
		'orp_notice_field_html', 'orp-site-notice', 'orp_main' );
}
add_action( 'admin_init', 'orp_settings_init' );

function orp_notice_field_html() {
	$value = get_option( 'orp_notice_text', '' );
	echo '<input type="text" name="orp_notice_text" value="'
		. esc_attr( $value ) . '" class="regular-text">';
}

function orp_settings_page_html() {
	?>
	<div class="wrap">
		<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
		<form action="options.php" method="post">
			<?php
			settings_fields( 'orp_settings' );
			do_settings_sections( 'orp-site-notice' );
			submit_button( 'Save Notice' );
			?>
		</form>
	</div>
	<?php
}

Das Formular postet an options.php, und der Core übernimmt das Speichern. Beachte, dass register_setting() auch ein default-Argument akzeptiert; wir setzen unseren Standardwert stattdessen im Activation-Hook – wähle also einen der beiden Mechanismen und bleibe konsistent.

Wie behandelt man den übermittelten Wert sicher?

Eine Berechtigungsprüfung beantwortet die Frage, ob dieser Nutzer Einstellungen speichern darf; eine Nonce beantwortet die Frage, ob dieser Nutzer dieses Formular tatsächlich absenden wollte. Eine Einstellungsseite, die auf einer Kundenwebsite bleibt, braucht beides, und Nonces dürfen die Berechtigungsprüfung niemals ersetzen.

Hier deckt die Settings API den größten Teil davon ab: settings_fields() gibt die Nonce aus, und der Core prüft sie, wenn das Formular zurückkommt; außerdem blockiert der Core das Speichern, sofern der aktuelle Nutzer nicht die Capability manage_options besitzt – jene Berechtigung, die wp-admin/options.php standardmäßig auf Einstellungsseiten anwendet. Wenn du stattdessen einmal einen eigenen Formular-Handler schreibst, gib die Nonce mit wp_nonce_field() aus und prüfe sie mit check_admin_referer(). Ergänze den expliziten Schutz im Page-Callback, analog zum Beispiel im Handbuch:

function orp_settings_page_html() {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}
	// ... form as above ...
}

Prüfe eine Capability, niemals einen Rollennamen wie administrator. Die beiden übrigen Schichten sind bereits vorhanden: Bereinigung auf dem Weg in die Datenbank (sanitize_text_field als sanitize_callback) und Escaping auf dem Weg hinaus (esc_attr() im Feld, esc_html() im Footer). Die beiden Operationen sind nicht austauschbar, und wer eine davon auslässt, macht aus einer gespeicherten Option ein Einfallstor für Injections.

Aktivieren und Funktion überprüfen

Aktiviere „Site Notice“ auf der Plugins-Seite, lade das Frontend und prüfe, ob der Hinweis im Footer erscheint. Ändere den Text unter „Einstellungen → Site Notice“ und lade neu. Wenn nichts passiert, arbeite diese Liste der Reihe nach durch:

  1. Ist das Plugin tatsächlich aktiviert und nicht nur vorhanden?
  2. Stimmt jeder String, der an add_action() oder add_filter() übergeben wird, exakt mit einem definierten Funktionsnamen überein?
  3. Gibt es Leerzeichen oder Ausgaben vor <?php? WordPress meldet in diesem Fall bei der Aktivierung „unexpected output“.
  4. Aktiviere WP_DEBUG in wp-config.php, zusammen mit WP_DEBUG_LOG, das Fehler nach wp-content/debug.log schreibt, und lies den tatsächlichen PHP-Fehler, statt zu raten.

Du hast nun ein Plugin mit einer Standardoption, einer Action, einem Filter, der seinen Wert zurückgibt, und einer Einstellungsseite, die über eine Nonce, eine Berechtigungsprüfung und einen Sanitizer speichert. Das ist die Grundlage, die es lohnt, für jedes Snippet zu übernehmen, das noch in der functions.php steckt: verschieben, mit einem Präfix versehen, an einen Hook hängen und das Paar „Sanitisierung hinein, Escaping hinaus“ intakt halten.

FAQs

Was ist der Unterschied zwischen wp_verify_nonce und check_admin_referer?

check_admin_referer() prüft sowohl die Nonce als auch den Referrer für Formulare und URLs auf Admin-Seiten und bricht die Anfrage mit einem 403 ab, wenn die Prüfung fehlschlägt. wp_verify_nonce() prüft nur die Nonce und gibt ein Ergebnis zurück, das du selbst behandelst – passend für Ajax-Handler und andere eigene Kontexte. Keine der beiden Funktionen ersetzt eine Berechtigungsprüfung: Nonces bestätigen, dass der Nutzer die Aktion beabsichtigt hat, current_user_can() bestätigt, dass er sie ausführen darf.

Was passiert mit den gespeicherten Optionen eines Plugins bei der Deaktivierung?

Nichts: Bei der Deaktivierung bleiben die Optionen in der Datenbank, sodass die Einstellungen erhalten sind, wenn der Nutzer das Plugin wieder aktiviert. Ein Deaktivierungs-Callback sollte nur kurzlebige Dinge entfernen, etwa Transients oder gecachte Dateien. Dauerhaftes Aufräumen gehört zur Deinstallation, umgesetzt entweder mit register_uninstall_hook() oder mit einer Datei uninstall.php im Plugin-Ordner. Wenn du uninstall.php verwendest, muss sie prüfen, ob die Konstante WP_UNINSTALL_PLUGIN definiert ist, bevor sie irgendetwas löscht.

Kann ein WordPress-Plugin mehr als eine PHP-Datei haben?

Ja, aber nur die Hauptdatei sollte den Plugin-Header-Kommentar enthalten. WordPress findet Plugins, indem es die PHP-Dateien im Plugin-Verzeichnis nach diesem Header durchsucht, und jede Datei, die einen enthält, erscheint als eigenes Plugin. Lade zusätzliche Dateien aus der Hauptdatei mit require_once und lasse Aufrufe wie register_activation_hook() auf die Hauptdatei des Plugins verweisen, da ihr erster Parameter auf diese Datei zeigen muss.

Sollte ein Standardwert in register_setting oder im Activation-Hook gesetzt werden?

Nutze einen Mechanismus, nicht beide. register_setting() akzeptiert ein default-Argument, das zurückgegeben wird, wenn in der Datenbank kein Wert existiert, während add_option() in einem Activation-Hook bei der Aktivierung einmalig einen echten Datenbankeintrag schreibt. Der Ansatz über die Aktivierung garantiert, dass der Wert bei jeder Anfrage existiert, auch bei Frontend-Anfragen; der Standardwert aus register_setting() greift nur dort, wo der Registrierungscode ausgeführt wurde. Beides zu mischen erzeugt zwei Quellen der Wahrheit für dieselbe Option.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.