Metaboxen eine CSS-Klasse zuordnen

Auf WPSE tauchte die Frage auf wie man eine Metabox im Backend per Voreinstellung minimiert darstellt. WordPress bietet hierfür keine Option an, obwohl es wahrscheinlich keine schlechte Idee wäre. Denn fügt man relativ viele Metaboxen ein, wird es schnell unübersichtlich. Um eine Metabox minimiert (geschlossen) darzustellen, benötigt sie die CSS-Klasse closed.
Auch wenn man eine Metabox in Abhängigkeit eines bestimmten Wertes besonders hervorheben möchte, z.B. weil eine Aktion beim Speichern fehlgeschlagen ist, bietet WordPress von Haus aus keine Option für zusätzliche CSS-Klassen an. Wer seine Metaboxen anders gestalten möchte, würde also wahrscheinlich auf JavaScript zurück greifen.

Die einfachste Lösung ist wie so oft ein Filter, der allerdings etwas versteckt ist. Die Funktion add_meta_box() selber hat keinerlei Filter oder Actions in die man sich einhängen könnte.
Dafür aber die Funktion die dafür zuständig ist die Titleleiste der Metabox darzustellen. Damit es etwas klarer ist um welchen Teil des HTMLs es hier geht, auszugsweise das HTML der Excerpt-Metabox:

<div id="postexcerpt" class="postbox">
  <div class="handlediv" title="Zum umschalten klicken">
    <h3 class="hndle">
      <span>Auszug</span>
    </h3>
   <div class="inside">
[...]
    </div> <!-- Ende div .inside -->
  </div> <!-- Ende div .handlediv -->
</div> <!-- Ende div #postexcerpt -->

Der Filter kann CSS-Klassen zum äußersten Div-Container, hier im Beispiel mit der ID postexcerpt, hinzufügen. Somit ist es dann auch möglich im Stylesheet die nachfolgenden HTML-Elemente zu stylen.

add_action( 'add_meta_boxes', 'add_my_metabox' );

function add_my_metabox() {
  $id       = 'my-metabox';
  $title    = 'My Metabox';
  $callback = 'my_metabox_content';
  $page     = 'post';

  add_meta_box( $id, $title, $callback, $page );

  add_filter( "postbox_classes_{$page}_{$id}", 'minify_my_metabox' );
}

function my_metabox_content() { ... }

/**
 * Add extra css-classes to a meta-box
 *
 * @param array $classes Array with css-classes
 */
function minify_my_metabox( $classes ) {
  array_push( $classes, 'closed' );

  return $classes;
}

In der Funktion add_my_metabox() wird zunächst einmal ganz normal eine Metabox erzeugt. Anschließend wird jedoch noch ein Filter gesetzt der als Callback die Funktion minify_my_metabox() aufruft. Der Hook für den Filter muss man um die ID der Metabox und den Post-Type ergänzen. An dieser Stelle wird es ein wenig kompliziert. Im Codex wird der Begriff “Post Type” verwendet, da der Filter und die zugehörige Funktion jedoch undokumentiert sind, muss man im Quellcode nachschauen. Und dort wird “page” als Variablenname verwendet.
Ich persönlich finde den Begriff “page” sinnvoller, da man mit der gleichen Methode auch z.B. die Widgets im Dashboard beeinflussen kann (siehe Beispiel unten). Als “page” müsste man dazu lediglich dashboard angeben und kann dann eigenen oder bereits vorhandenen Widgets, weitere CSS-Klassen hinzufügen. Im Codex findet man bei remove_meta_box() noch ein paar weitere mögliche Werte für page. Hier wird übrigens wieder von page gesprochen, im Gegensatz zu post type bei add_meta_box().

function register_dash_widget(){

	$id   = 'debugoutput';
	$page = 'dashboard';

	wp_add_dashboard_widget(
		$id,
		'Debug Output',
		function () { echo 'Hello World!'; }
	);

	add_filter(
		"postbox_classes_{$page}_{$id}",
		function ( $classes ) {
			array_push( $classes, 'closed' );
			return $classes;
		}
	);

}

add_action( 'wp_dashboard_setup', 'register_dash_widget' );

Die Callback-Funktion minify_my_metabox() ist dann wieder recht unspektakulär. Sie erwartet als einzigen Parameter ein Array mit den CSS-Klassen. Fügt man z.B. die Klasse closed hinzu und gibt das Array zurück, so wird die Metabox (oder das Widget) minimiert anstatt geöffnet dargestellt. Denkbar wäre es natürlich auch eine CSS-Klasse alert hinzuzufügen um eine Metabox (oder Widget) farblich hervorzuheben.


Mit Git und dem WordPress-SVN arbeiten

In den vergangenen Tagen habe ich mein erstes Plugin im WordPress Repository veröffentlicht. Für jemanden der wie ich schon seit Version 1.2 mit WordPress arbeitet, eigentlich ein recht später Zeitpunkt. Die Gründe hierfür sind jedoch weitreichend und sehr verschieden. Neben den sehr restriktiven bürokratischen Hürden war vor allem der Umstand das es sich beim WordPress Repository um ein SVN-Repository handelt ein Grund dort keinen Code einzureichen. Warum WordPress unbedingt ein SVN-Repo betreiben muss, erschließt sich mir nach wie vor nicht. Es würde reichen wenn sie Zip-Archive der Plugins hosten, alles was ein SVN-Repo an Möglichkeiten bietet, könnte man aus dem Zip-Archiv ableiten. Aber egal, sei’s drum.
Das allerdings nicht nur ich eine Abneigung gegen SVN habe, zeigte sich schnell in einer kleinen Diskussion die sich entwickelte als ich zu meinen Plugin auf Google+ einen Beitrag veröffentlichte. Kernpunkt der Diskussion war, wie man am besten Git und SVN unter einen Hut bekommt.
Die Schwierigkeit dabei ist gar nicht einmal beide unter einen Hut zu bekommen, dafür gibt es git svn, sondern auch noch die zusätzlichen Hürden zu überwinden die von WordPress aufgestellt wurden. So darf man z.B. nicht jeden Commit den man durchführt ins WP-Repo überführen, sondern lediglich wenn man eine neue Version veröffentlicht. Zudem muss man die neue Version taggen. git svn kann weder das eine noch das andere wirklich sauber durchführen. Nutzt man git svn, muss man vor jeden Commit ins SVN-Repo die Commit-History bereinigen (squashen), taggen muss man dann sogar von Hand da auch hier git svn nicht sehr sauber arbeitet. Es mag sein das git svn dies sehrwohl kann und ich es einfach nicht heraus bekommen habe wie man das sauber hin bekommt, da lasse ich mich gerne eines besseren belehren, jedoch hatte ich irgendwann auch einfach keine Lust mehr mich mit git svn auseinander zusetzen.

Ein paar Basics

Im Grunde genommen nutze ich immer zwei Git-Repos. Zum einen ein lokales Repo im Ordner in dem ich den Code entwickele. Zum anderen ein entferntes (remote) Repo in dem ich meine Arbeit speichere. Viele nutzen als Remote-Repo GitHub, BitBucket oder einen Firmenserver. Ich nutze aus verschiedenen Gründen einen Ordner in meiner Dropbox. Da im späteren Verlauf eine lokale Kopie des SVN-Repos benötigt wird, empfiehlt es sich neben den üblichen Remote-Repos (GitHub, BitBucket, usw.) ein Remote-Repo auf der eigenen Festplatte einzurichten. Das muss nicht zwingend in einer Dropbox sein, bietet sich jedoch an wenn man, so wie ich zum Beispiel, an mehreren Rechnern oder im Team an einem Plugin arbeitet.
Beispielhaft sieht die Ordnerstruktur nun so aus das /d/local/plugin-name/ der Ordner ist in dem entwickelt wird und /d/dropbox/plugin-name/ der Ordner für das Remote-Repo darstellt. Um es etwas einfacher zu machen verwende ich ‘local‘ als Namen für den lokalen Entwickelungs-Ordner und ‘remote‘ als Namen für den Ordner der das Remote-Repo enthält.

Hooks

Um das automatische Committen ins SVN-Repo durchzuführen hatte ich recht schnell git hooks als Lösungsweg ausgespäht. Dank einer guten Dokumentation zu Git und sich selbst installierende Beispielen, fand ich dann auch schnell einen passenden Hook und wie ich ihn zu meinen Zwecken nutzen kann. Bei dem Hook handelt es sich um den update hook, der immer dann ausgeführt wird, wenn in ein Remote-Repo hinein gepusht wird. Um das zu verstehen, ein kurzer Einblick in die Hooks von Git.
Es gibt Hooks die nur dann ausgeführt werden wenn eine Aktion im lokalen Repo durchgeführt wird (z.B. beim Committen). Und es gibt Hooks die nur im Remote-Repo ausgeführt werden (z.B. beim Pushen). Die Hooks unterscheiden sich allerdings nicht nur darin wann (und wo) sie ausgeführt werden, sondern auch darin, welche Parameter sie mitgegeben bekommen. Da es keinen Hook gibt den man im lokalen Repo nutzen kann sofern man feststellen möchte ob ein Tag gesetzt bzw. gepusht wurde, entschied ich mich auf das Remote-Repo auszuweichen wo es den update hook gibt der von Git die benötigten Parameter übergeben bekommt.

Das Setup

Nun müssen noch ein paar Kleinigkeiten eingerichtet werden. Das git init im local Ordner dürfte mittlerweile in Fleisch und Blut übergegangen sein. Als nächstes wird mit einer Zeile sowohl das remote Verzeichnis als auch die Kopie des SVN-Repos angelegt. Dazu nach /d/dropbox/ wechseln, eine Shell öffnen und svn checkout http://plugins.svn.wordpress.org/plugin-name eingegeben. SVN erzeugt einen Ordner plugin-name der bereits alle für SVN benötigten Unterordner enthält. Da nun noch das Remote-Git-Repo fehlt, wird in den Ordner plugin-name gewechselt und mit git init --bare ein Remote-Git-Repo angelegt. Wer es mal ausprobiert, wird merken das git svn im Grunde genommen nichts anderes macht. Es werden einfach zwei Repos unterschiedlicher CVS in einen Ordner untergebracht. Zum Schluß wird der remote noch in local als Remote-Repo hinzugefügt: git remote add dropbox /d/dropbox/plugin-name
Jetzt ist das Setup fast geschafft, was noch fehlt ist das Script für den update hook. In /d/dropbox/plugin-name/hooks/ finden sich einige Beispiele für git hooks. Die können alle gelöscht werden um es schön übersichtlich zu halten. Statt der Beispiel-Scripte wird das update Script hier hinein kopiert und damit ist das Setup abgeschlossen.

Jetzt können wir fleißig an unseren Code im Entwickelungs-Ordner local arbeiten. Als gute Entwickler committen wir oft und aussagekräftig. Am Ende eines jeden Tages (oder auch öfters), pushen wir unsere Arbeit in den remote. Wenn man nun einmal in den remote schaut, wird man merken das sich nichts (sichtbares) dort tut. Weder kommt Code hinzu noch wird ein SVN-Commit durchgeführt (lässt sich ganz einfach mit svn log prüfen). Dies ist jedoch auch genau so gewollt. WordPress verbietet es uns jeden Commit in das WP-SVN zu committen.
Wie kommt nun unsere neue Version ins WP-SVN? Wenn es denn soweit ist das eine neue Version fertig ist, dann wird diese in Git getaggt: git tag 1.0 -a -m 'A new version of plugin-name was released with version number 1.0' oder mit einen schlichteren “un-annotated tag” git tag 1.0. Dieser Tag muss noch nach remote gepusht werden (git push dropbox 1.0), ab jetzt fängt die Magie des update hooks an zu wirken.
Das Shell-Script macht nun folgendes:

  • Prüfe ob ein Git-Repo im Ordner trunk vorhanden ist. Wenn nicht, dann erzeuge eines
  • Prüfe ob in diesen Git-Repo ein Remote-Repo namens svn-master vorhanden ist. Wenn nicht, dann füge es hinzu
  • Hole dir die Daten aus dem Remote-Repo svn-master in den master branch (pull = fetch&merge)
  • Füge den neuen Code/Dateien im SVN-Repo hinzu und führe ein SVN-Commit durch
  • Erzeuge mit svn copy einen sauberen SVN-Tag im Remote-SVN-Repo

Das update Script muss dabei auf einen kleinen Trick zurück greifen. Tags sind nichts anderes als Revisionen die einen bestimmten Zustand des Codes widerspiegeln. Normalerweise könnte man beim pull auch auf den Tag zugreifen, dieser existiert im Remote-Repo allerdings zu diesen Zeitpunkt noch nicht. Deswegen muss der Code erst in einen Commit zum Remote-Repo gepusht werden, da der Code anschließend aus den letzten Commit gezogen wird!
Es gibt sicherlich Möglichkeiten auch Clientseitige Hooks, also Hooks die im lokalen Repo ausgeführt werden, zu nutzen. Dann müsste man jedoch bei jedem klonen des Remote-Repos diese Hooks neu einrichten. So läuft der Prozess mehr oder minder zentral ab und man muss alles nur einmal einrichten egal mit wie vielen Computern oder Teammitgliedern man das ganze nutzt.

Fazit

Das ganze ist noch etwas wackelig, vor allem was Merge-Konflikte angeht. Mir fehlen dazu bisher die praktischen Erfahrungen und weitere Tests. Da das Git-Repo im Verzeichnis trunk jedoch einzig und alleine dazu dient den Code in das trunk Verzeichnis zu kopieren, kann man hier sehr grob vorgehen und einfach den bestehenden Code gnadenlos überschreiben. Es finden sich dazu mehrere Methoden im Netz, ich bin mir nur noch nicht ganz sicher welche die beste ist. Wahrscheinlich ist es das Beste den Code aus dem Remote-Repo in einen neuen, temporären Branch zu kopieren, den SVN-Commit durchzuführen und den Branch anschließend wieder zu löschen. Wer es ganz hart mag, kann das Git-Repo auch einfach komplett löschen indem er den Ordner .git im Verzeichnis trunk löscht.
Auf ein svn update verzichte ich bewusst, da dies ebenfalls zu Merge-Konflikten führen kann. Meine Lösung ist also nichts für diejenigen, die mit mehreren an einen WP-SVN-repo arbeiten. Hier sind andere Strategien gefragt die um etwas Handarbeit, vor allem beim Lösen von Merge-Konflikten, nicht drum herum kommen.

Diejenigen die sich mit Shell- bzw. Bash-Scripten auskennen, werden schnell erkennen das dies mein allererstes Bash-Script ist. Der Code ist definitiv nicht optimal, erfüllt jedoch seinen Zweck. Auch lässt sich das eine oder andere in Sachen SVN vielleicht noch optimieren, denn auch SVN war bisher nicht mein Fachgebiet (wird es wohl auch nie werden).
Ein paar Kleinigkeiten sollten noch gelöst werden, so zum Beispiel das automatische Übernehmen der Commit-Nachricht bei annotated Tags. In meinen Augen aber eher Komfort-Funktionen die eine etwas geringere Priorität haben.
Das es ein Repo ist, kann sich ja ohnehin jeder den Code ziehen und die Verbesserungen/Optimierungen einbauen die er meint das sie dort hin gehören. Da es sich um “Social Coding” handelt, würde es mich freuen wenn auch der eine oder andere Pull Request bei mir ankommt.


FrankenKey

Aus einer Frage auf WordPress.Stackexchange.com heraus habe ich ein kleines Plugin geschrieben. Mich hat es auch immer ein wenig genervt das der HTML-Editor von WordPress keine richtigen Tastaturkürzel hat. Es gibt lediglich Access-Keys, was bedeutet das beim Drücken einer bestimmten Tastaturkombination der Fokus vom Textfeld auf einen der Buttons in der Werkzeugleiste verlegt wird. Dadurch muss man noch einmal Enter drücken damit die Aktion ausgeführt wird. Zudem verrenke ich mir immer die Finger beim Drücken von z.B. [shift]+[alt]+[a] um einen Link einzufügen. Mal ganz davon abgesehen das ich es mir nur sehr selten merken kann welche Tastaturkombination welche Aktion ausführt.

Als kleines Weihnachtsgeschenk für diejenigen die es brauchen können habe ich FrankenKey veröffentlicht. Das Plugin ist eher als “raft draft” anzusehen, also ein Entwurf für ein Plugin das noch im Fluss der Entwickelung ist. Wer es dennoch schon mal testen will oder sein eigenes Plugin darauf aufbauen möchte, ist gerne eingeladen daraus seine eigene Lösung zu erstellen.

Technisch gesehen ist das Plugin sehr simple aufgebaut. Die Tastatur wird mit mousetrap überwacht. Es gibt zwar noch ein jQuery Plugin namens jQuery Hotkeys welches auch von WordPress bereitgestellt wird, dieses ist aber nicht ganz so leistungsfähig wie mousetrap und ich habe es auch nicht richtig zum Laufen bekommen.
selection.js übernimmt die Arbeit der Modifikationen im Text. Es ist ein jQuery-Plugin welches auf Code von Stackoverflow basiert. selection.js liefert den ausgewählten Text und kann diesen zum Teil auch modifizieren (z.B. in Tags einschließen).
Die Kernkomponente frankenkey.js arbeitet wie eine Weiche. Hier werden die Bindungen zwischen Textfeld und mousetrap angelegt und letzten Endes auch die Tastatureingaben verarbeitet. Wird eine Tastaturkombination erkannt, wird zuerst entschieden ob es sich um eine Kombination zum Einfügen von Tags oder eines simulierten Klicks auf einen der Buttons handelt. Grundsätzlich hätte man das Script so gestalten können, dass es lediglich Klicks auf die Buttons in der Werkzeugleiste simuliert. Dadurch das eigene Aktionen definiert werden, können auch andere HTML-Tags als die vordefinierten verwendet werden.

Mich würde mal interessieren ob es so was überhaupt braucht oder ob es völlig überflüssig ist. Was denkt ihr darüber?


Ernsthaft, Andrew?

WordPress 3.5 bringt viele Neuerungen mit sich. Unter anderem auch eine Fehlermeldung: Warning. Missing argument 2 for wpdb::prepare(). Nun ist das ganze keine “richtige Fehlermeldung”, sondern eine Information an den Benutzer. Dennoch verwirrt es viele Nutzer da sie es doch für eine Fehlermeldung halten und befürchten das ihre Plugins oder Themes nun nicht mehr funktionieren. Obwohl es nur ein Hinweis ist, kann es dennoch dazu führen das einige Plugins nicht mehr funktionieren. Zum Beispiel in dem Fall, wenn eine Ajax-Routine ihre Ergebnisse mittels echo oder print anstatt als wohlgeformtes JSON zurückgeben.

Der Grund für diese Änderung lag darin, dass man verhindern wollte das prepare() durch falsche Anwendung anfällig für SQL-Injections ist.

$wpdb->prepare( "SELECT * FROM table WHERE id = $id" );

See the problem? That query isn’t secure! You may think you are “preparing” this query, but you’re not — you’re passing $id directly into the query, unprepared.

Weiter heißt es:

Because you can’t prepare a query without more than one argument.

Ich halte die Änderung vom Prinzip her für sinnvoll. Die Umsetzung jedoch für hektischen Aktionismus. Schauen wir uns das ganze doch einmal näher an und fangen mit der zweiten zitierten Aussage an.

Keine Hühner ohne Eier oder keine Eier ohne Hühner?

Man kann eine SQL-Abfrage nicht vorbereiten wenn man prepare() mit nur einen Argument aufruft. Im Umkehrschluß würde dies bedeuten, jede SQL-Abfrage wird vorbereitet wenn man zwei Argumente übergibt. Stimmt das?

$wpdb->prepare( "SELECT * FROM table WHERE id = $id", $id );

Das soll jetzt also besser sein? Die Abfrage wird ebenfalls nicht vorbereitet und ist genauso anfällig für SQL-Injections. Das Blöde an der Geschichte ist, dass der eine oder andere Plugin-Autor sich mit der PHP-Warnung an sein lieblings PHP-Forum wendet anstatt in den Codex zu schauen. Dort wird man ihm sagen – was ja im Grunde genommen auch richtig ist – dass er einen zweiten Parameter übergeben muss, nichts anderes sagt schließlich die PHP-Warnung aus. Das Ergebnis sieht dann so aus wie das Beispiel oben.
Hier wäre doch der Umkehrschluss – keine Vorbereitung der SQL-Abfrage wenn sie keinen Platzhalter enthält – besser gewesen. Diese Prüfung lässt sich relativ einfach mit einem Regulären Ausdruck realisieren und es kann anschließend ein WordPress-Error zurück gegeben oder eine PHP-Warnung ausgegeben werden.
Um das Rad rund zu machen, kann man weiterhin prüfen ob im Query-String ein $-Zeichen gefolgt von einer Reihe alphanumerischen Zeichen, also ein Variablenname, vorkommt. Dies würde jedoch eine weitere Änderung benötigen zu der ich gleich komme.
Das Ergebnis ist nahezu das gleiche. Falsch programmierte Plugins funktionieren nicht mehr (richtig) und die Plugin-Autoren werden dazu gezwungen die richtige Syntax zu verwenden.

Ist die Lücke wirklich geschlossen?

Bisher ist es meiner Ansicht nach eher Geschmackssache. Mir schmeckt es halt nicht wirklich. Ziel der Anforderung ist jedoch erreicht und damit gut.
Was ich weniger toll finde, ist der Umstand das SQL-Injections weiterhin möglich sind und die derzeitige Lösung dies eben nicht verhindert. Das Problem ist der Tabellenname.

In allen Beispielen, auch im Blogpost von Andrew Nacin, sieht man in der SQL-Abfrage ‘table‘ als Angabe für den Tabellennamen. Leider heißt keine einzige Tabelle in der WP-Datenbank ‘table‘. Und obendrein kann der Präfix je nach Installation anders lauten. Wie bildet man den Tabellennamen also richtig? Zum Beispiel mit

$post_table = $wpdb->posts

(siehe Codex)
Und wie bekommt man den Tabellennamen in seine SQL-Abfrage? Wahrscheinlich so:

$prepared_query = $wpdb->prepare( "SELECT * FROM $post_table; WHERE id = %d;", $post_id );

Leider gibt es auch im professionellen Umfeld noch Autoren die solche Sachen machen:

/// OUCH!!!!
$post_table = $_GET['table'];

// build a query with possible sql-injection
$query = "SELECT * FROM $post_table WHERE id = %d;";

Wer garantiert denn das ein gültiger Tabellenname übergeben wird? Man braucht ja nur etwas Code an das Ende des Tabellennamens anhängen und schon sieht die Abfrage z.B. so aus:

SELECT * FROM wp_posts; DROP TABLE wp_posts;-- WHERE id = %d"

Wäre doch schade wenn es jemanden gelingen würde ein DROP TABLE (oder schlimmeres) an den Tabellennamen anzuhängen.

Jetzt könnte man ja geschwind der Meinung sein, dass man den Tabellennamen auch mit dem Platzhalter %s einfügen könnte.

$post_table = $_GET['table'];
$post_id = $_GET['id'];

$prepared_query = $wpdb->prepare( "SELECT * FROM %s WHERE id = %d;", $post_table, $post_id );

Tja, leider nicht. Denn das ergibt dann eine Abfrage wie z.B.:

SELECT * FROM 'wp_posts' WHERE id = 5;

Und diese Abfrage führt zu einen MySQL-Fehler denn der Tabellenname darf natürlich nicht in Anführungszeichen stehen. Der Plugin-Autor muss sich an dieser Stelle selber darum kümmern das der Tabellenname vorhanden ist und keinen Schadcode enthält. Wie viele Plugin-Autoren haben das bisher gemacht? Wohl die wenigsten.

Was fehlt

Es fehlt ein Platzhalter für den Tabellennamen (z.B. %t). prepare() könnte die vorhandenen Tabellennamen abfragen und somit gleichzeitig prüfen ob eine solche Tabelle vorhanden ist. Dies würde dann die Gefahr für SQL-Injections tatsächlich gegen Null tendieren lassen.
Zudem wäre die Prüfung auf einen vorhandenen Platzhalter deutlich sinnvoller. Es ist zwar nicht die bessere, meines Erachtens nach aber die sinnvollere, Lösung.
Als Fazit muss man also festhalten das sich im Grunde genommen nichts gebessert hat. Lediglich etwas Verwirrung ist unter den Benutzern entstanden und der eine oder andere Plugin-Autor wird wohl ein paar E-Mails bekommen.
Ich zweifele ja daran das entsprechende Tickets im Trac sinnvoll sind (Erfahrungswert), werde jedoch mal ein oder zwei schreiben. Falls jemand schneller ist (oder sein möchte ;) ), hinterlasst die Ticket-Nummer bitte in den Kommentaren. Ich werde mich dann den Tickets aktiv anschließen.


Eigene Felder bei der Benutzerregistrierung

Es kann durchaus mal vorkommen das bei der Registrierung von Benutzern Felder benötigt werden die nicht vorhanden sind. Normalerweise benutzt man die Seite auf der man Benutzer bearbeitet (user edit screen) um zusätzliche Benutzerdaten abzulegen. Das ist aber manchmal etwas umständlich, da man erst den Benutzer anlegen und ihn in einen zweiten Schritt bearbeiten muss. Deutlich einfacher wäre es, könnte man Registrierung und zusätzliche Benutzerdaten in einen Durchgang erledigen. Diese Fragestellung tauchte auf WordPress StackExchange auf und ich machte mir ein paar Gedanken darüber.

Mein erster Gedanke war, nein geht nicht. Denn das Registrierungsformular bietet weder Hooks noch Filter an die man nutzen könnte um eigene Felder einzufügen. Nun ist das Einfügen einer Tabellenzeile und eines Input-Feldes mit jQuery keine Raketenwissenschaft, beides ist mit ein paar Zeilen Code schnell erledigt.

<?php
/**
* Plugin Name: Custom user registration fields
* Plugin URI: http://yoda.neun12.de
* Description: Add custom fields to the user registration
* Version: 0.1
* Author: Ralf Albert
* Author URI: http://yoda.neun12.de
* Text Domain:
* Domain Path:
* Network:
* License: GPLv3
*/

add_action( 'plugins_loaded', 'wp_custom_user_registration_fields', 10, 0 );


function wp_custom_user_registration_fields(){

add_action(
'admin_print_scripts-user-new.php',
function (){

wp_enqueue_script(
'add_custom_user_registration_field',
plugins_url( 'wp_curf.js', __FILE__ ),
array( 'jquery' ),
false,
true
);

wp_enqueue_script( 'wp_curf_l10n' );

wp_localize_script(
'wp_curf_l10n',
'wp_curf_l10n',
array(
'label' => _( 'Biographical Info' ),
'description' => _( 'Share a little biographical information to fill out your profile. This may be shown publicly.' )
)
);

}
);

}

view raw index.php This Gist is brought to you using Simple Gist Embed.
/**
* jQuery part of Costum User Registration Field
*
* @author Ralf Albert
* @version 0.1
*/

jQuery( document ).ready(

    function($){

        var insertElements =
            '<tr class="form-field">' +
            ' <th scope="row"><label for="description">' + wp_curf_l10n.label + '</label></th>' +
            ' <td><textarea name="description" id="description" rows="5" cols="30"></textarea><br /><span class="description">' + wp_curf_l10n.description + '</span></td>' +
            '</tr>';

        $( '#createuser .form-table tbody' ).append( insertElements );

    }

);
view raw wp_curf.js This Gist is brought to you using Simple Gist Embed.

Das der Code so schlank und kompakt aussieht, liegt daran das ich die “Biographischen Angaben” aus dem Benutzer-Profil, also ein Standardfeld von WordPress, verwendet habe. WordPress verwendet sowohl für das Anlegen als auch für das Bearbeiten eines Benutzers nahezu die gleiche Routine. Somit muss ich mich nicht um die Speicherung des Feldinhaltes kümmern.
Will ich nun ein komplett eigenes Feld erstellen, nennen wir es mal “Abteilung”, muss ich mich wieder um die Speicherung des Feldinhaltes und auch die Anzeige im Benutzerprofil, kümmern. Wie man eigene Felder dem Benutzerprofil hinzufügt und diese anzeigt, dazu gibt es im Netz zahlreiche Tutorials, zum Beispiel bei WP Engineer.
Da aber bereits bei der Registrierung eines Benutzers Daten gespeichert werden sollen, muss noch ein zusätzlichen Hook bemüht werden: user_register. Als Callback kann hier wieder die gleiche Funktion benutzt werden die auch beim Bearbeiten eines Benutzers verwendet wird, so sparen wir uns ein paar Zeilen Code.

Alles zusammen findet sich in diesen Gist und kann als Ausgangspunkt für ein eigenes Plugin verwendet werden.