Im WP LETTER von gestern wurde ein Artikel verlinkt, in dem ein sehr einfacher Weg beschrieben wird, um eine Meta-Box zu erstellen. Auch ich habe vor einigen Monaten einen ähnlichen Ansatz umgesetzt, ihn aber bisher noch nicht vorgestellt. Das möchte ich nun an dieser Stelle nachholen und euch zeigen, wie einfach Meta-Boxen sind.
Wiederverwendung von Code
Ein wichtiges Prinzip in der Programmierung ist das DRY-Prinzip (Don’t repeat yourself). Dabei werden Codeteile, die man häufiger verwendet in Funktionen oder Klassen implementiert, um Redundanzen zu vermeiden und die Pflege des Codes zu vereinfachen.
Als ich das erste Mal mit Meta-Boxen zu tun hatte war ich nicht zufrieden damit, dass man viele Funktionen und Überprüfungen ständig dupliziert und für jede Meta-Box neu umsetzen muss. Ich habe daher versucht, diese in einer Basisklasse zu vereinen. Darüber hinaus habe ich zusätzlich ein Interface definiert, um eine falsche Implementierung dieser Klasse zu vermeiden.
Das Interface
Jede Meta-Box benötigt mindestens eine Funktion um die Box anzuzeigen und eine, um die eingegebenen Daten zu speichern. Bei meiner Umsetzung sind das die Funktionen register()
, render()
, save()
und check()
. Das gesamte Interface sieht wie folgt aus:
interface Meta_Box_Interface { public function register(); public function render( $post ); public function save( $post_id ); public function check(); }
Die Meta-Box Basis Klasse
Alle Funktionen, die jede Meta-Box benötigt wurden in einer Basis-Klasse bzw. weiteren Spezial-Klassen implementiert. Die Basis-Klasse hat folgende Funktionen:
class Meta_Box { protected $key; private $label; private $post_type; private $position; private $priority; protected $nonce_validator; protected $request_validator; public function __construct( $key, $label, $post_type, $position, $priority = 'default' ) { $this->key = $key; $this->label = $label; $this->post_type = $post_type; $this->position = $position; $this->priority = $priority; $this->nonce_validator = new Nonce_Validator( $this->key . '_name', $this->key . '_action' ); $this->request_validator = new Request_Validator( 'edit_' . $this->post_type . 's' ); add_action( 'save_post', array( $this, 'save' ) ); add_action( 'add_meta_boxes', array( $this, 'register' ) ); } public function register() { add_meta_box( $this->key, $this->label, array( $this, 'render' ), $this->post_type, $this->position, $this->priority ); } public function check() { return $this->request_validator->is_valid() && $this->nonce_validator->is_valid(); } }
Da jede Meta-Box mit der Funktion add_meta_box()
hinzugefügt werden muss und die Gültigkeit validiert werden sollte, sind diese beiden Funktionen in Basis-Klasse ausgelagert. Weiterhin werden hier im Konstruktor die Hook zum Hinzufügen und Speichern gesetzt.
Eine Implementierung der Basis-Klasse
Eine individuelle Meta-Box nutzt dann diese Klassen und muss, neben dem Konstruktor, im einfachsten Fall nur die beiden Funktionen zum rendern und speichern aus dem Interface implementieren. Für ein Projekt sollte eine Funktion umgesetzt werden, die es ermöglicht, einen Nutzer (nicht den Autor) mit einer Seite zu verknüpfen. Die Meta-Box sah dabei in etwa wie folgt aus:
class Contact_Person_Meta_Box extends Meta_Box implements Meta_Box_Interface { public function __construct( $post_type = 'page' ) { parent::__construct( 'contact_person_meta_box', __( 'Contact Person', 'contact-person-meta-box' ), $post_type, 'side', 'low' ); } function render( $post ) { wp_nonce_field( $this->nonce_validator->get_action(), $this->nonce_validator->get_name() ); $contact_person_id = get_post_meta( $post->ID, '_contact_person_id', true ); echo '<label class="screen-reader-text" for="contact_person_id">' . esc_html__('Contact Person', 'contact-person-meta-box' ) .'</label>'; wp_dropdown_users( array( 'name' => 'contact_person_id', 'selected' => $contact_person_id, 'include_selected' => true, 'show_option_none' => __( '— Select —' ), ) ); } public function save( $post_id ) { if ( ! self::check() ) { return; } $contact_person_id = intval( $_POST[ 'contact_person_id' ] ); update_post_meta( $post_id, '_contact_person_id', $contact_person_id ); } }
Im Konstruktor vergeben wir für die Meta-Box einen Slug sowie einen Namen. Zusätzlich definieren wir, für welche Post-Types die Meta-Box angezeigt werden soll und an welcher Position (diese kann jeder Nutzer später per Drag&Drop verändern. Mit diesen Angaben rufen wir dann den Konstruktor der Basis-Klasse auf. In der render()
Funktion implementieren wir die HTML Formularfelder der Box. Hierbei sollte man neben geeigneten Input-Types auch die passenden CSS-Klassen verwenden, die auch der WordPress-Core verwendet. Zuletzt kümmert sich die save()
Funktion darum, die Daten aus der Meta-Box zusammen mit dem Rest des Post-Types zu speichern. In beiden Funktion ist darauf zu achten, dass die Daten korrekt „sanitized“ und „escaped“ werden.
Die Meta-Box instanziieren
Damit die Meta-Box überhaupt verfügbar ist, muss sie auch instanziiert werden. Hierzu rufen wir einfach den Konstruktor in einem Hook auf:
function init_contact_person_meta_box() { new Contact_Person_Meta_Box(); } add_action( 'init', 'init_contact_person_meta_box' );
Das Ergebnis
Mit nur einer Klasse und zwei sehr kurzen Funktionen kann man also sehr einfach eine Meta-Box erstellen. Der Code oben ist an manchen Stellen stark vereinfacht. Eine vollständige Implementierung findet ihr natürlich wie immer in einem GitHub Repository, wo ihr euch auch wieder ein fertiges Plugin als ZIP-Datei runterladen könnt. Was das Plugin nicht enthält ist eine Ausgabe des Nutzers im Frontend. Wir haben dazu ein Widget verwendet. Wie ihr ein solches einfach erstellen könnt, habe ich euch ja gestern gezeigt. Ich lasse euch das gerne mal selbst versuchen 🙂
Ich hoffe ich konnte euch mit diesem Beitrag ein wenig die Angst davor nehmen, einmal selbst eine Meta-Box zu bauen. Falls dabei etwas ähnlich Nützliches herausspringt, dann teilt es gerne hier in einem Kommentar.