Login überall

Nicht wenige WordPress-Nutzer wünschen sich die Möglichkeit sich auch z.B. auf der Startseite einloggen zu können anstatt erst die Login-Seite aufrufen zu müssen. WordPress trägt dem Rechnung und stellt die Funktion wp_login_form zur Verfügung. Für einfacher gelagerte Fälle keine schlechte Lösung, jedoch für etwas anspruchsvollere Geister nicht unbedingt das was sie suchen. Denn im Fehlerfall, z.B. bei einem falschen Passwort, landet man doch wieder auf der Login-Seite.
Schöner wäre es, würde der komplette Login-Prozess genau dort statt findet, wo das Login-Formular angezeigt wird. Wer sich nicht scheut ein Iframe zu benutzen, der kann folgende vergleichsweise einfache Lösung benutzen.

Login im Iframe

Im Grunde genommen kann man den ganzen Login-Prozess in einem Iframe ablaufen lassen. Man muss sich dabei lediglich vor Augen halten das ein Iframe mit einem neuen Tab im Browser vergleichbar ist. Dem Server ist es jedoch egal ob man den Login in einem neuen Tab oder Iframe vornimmt. Ob ein Benutzer eingeloggt ist oder nicht, wird in einem Cookie gespeichert. Dadurch ist der “Ort” wo man sich einloggt uninteressant, so lange er vom gleichen Server aus erfolgt.

Fangen wir also gleich damit an die wp-login.php in einem Iframe anzuzeigen. Damit dies ein wenig komfortabler wird, legen wir uns eine Funktion an die wir in der functions.php oder einem Plugin ablegen können.

function my_login_form( $args ){

	extract( $args );

	if( !isset( $width ) ){
		$width = 300;
	}

	if( !isset( $height ) ){
		$height = 300;
	}

	if( !isset( $redirect ) ){
		$redirect = content_url() . '/themes/my_theme/logout.php';
	}

	echo '<iframe src="http://www.example.com/wp-login.php?redirect_to=' . $redirect . '&iframe_mode=true" width="' . $width . '" height="' . $height . '">';
}

Diese Funktion macht eigentlich nichts anderes als ein Iframe anzuzeigen welches als Source die wp-login.php hat. Die Zeilen 3-16 dienen lediglich dazu ein paar Parameter an die Funktion zu übergeben. Im Beispiel oben sind das width (Breite des Iframe), height (Höhe des Iframe) und redirect_to (Ziel-URL zu der nach dem Login umgeleitet werden soll). Diese Parameter werden per Array übergeben:

if( function_exists('my_login_form') ){
	$args = array( 'width'=>300,
		       'height'=>200,
			redirect_to=> content_url().'/themes/my_theme/mylogin_redirect.php' );
	my_login_form( $args );
}

Fügen wir obigen Code-Schnipsel in unser Blog ein, z.B. in der Sidebar, dann fügt die Funktion an dieser Stelle ein Iframe mit der Breite 300px und Höhe 200px ein. Bis hierhin nichts wirklich aufregendes, einzig der Parameter redirect_to tanzt ein wenig aus der Reihe.
Wir erinnern uns, die Anzeige im Iframe ist mit einem neuen Tab vergleichbar. Die Situation ist nun folgende: Innerhalb des Blogs wird die wp-login.php angezeigt, beim Betätigen des  Submit-Buttons leitet die wp-login.php, sofern die Login-Daten korrekt sind, automatisch auf eine andere Seite um. Diese Seite wird dann ebenfalls im Iframe angezeigt! Normalerweise wird auf die Profil-Seite des Benutzers bzw. ins Dashboard umgelietet, in einem kleinen Iframe dargestellt sieht dies eher unbrauchbar aus. Diese Umleitung kann man jedoch beim Aufruf der wp-login.php mit dem Parameter redirect_to beeinflussen.
Dies machen wir uns zu nutzen und zeigen einfach eine eigene Seite an deren Inhalt besser in das Iframe passt. Diese Datei hat noch eine weitere recht wichtige Aufgabe. Denn durch den Aufruf von wp-login.php würden wir eine weitere Instanz von WordPress erzeugen was zu unschönen Nebeneffekten führen könnte. Ich habe hier als Beispiel mal den Benutzernamen und einen Logout-Link ausgegeben. Wichtig ist, dass das Script mit einem exit; beendet wird:

<?php
global $current_user;

echo $current_user->user_name . '<br />';
echo '<a href="' . wp_logout_url( site_url().'/wp-login.php' ) . '" title="Logout">' . __('Logout') . '</a>';
exit;
?>

Durch die Angabe von site_url . '/wp-login.php' wird beim Betätigen des Logout-Links (innerhalb des Iframes) wieder auf die Login-Seite umgeleitet.

Fehlende Optik

Damit haben wir uns bis jetzt ein schönes Karussell gebastelt.  Im Iframe wird die wp-login.php angezeigt, nach einem erfolgreichen Login wird unsere eigene Seite angezeigt und nach dem Ausloggen wieder die wp-login.php.
Jetzt bleibt noch ein kleines aber nicht unbedeutendes Problem übrig. Die wp-login.php würde im Iframe mit dem originalem Stylesheet angezeigt werden, dieses ist aber nicht auf Miniformate wie z.B. 300x200px ausgelegt. Zudem haben wir nun ein paar Elemente drin auf die wir gerne verzichten würden. Ein angepasstes Stylesheet muss also her. Wie man den Login-Screen optisch an seine eigenen Bedürfnisse anpasst, dazu finden sich im Internet massig Anleitungen. Zur Not einfach Bei Frank Bültge vorbei schauen, dort findet sich (fast) immer etwas passendes.
Ein kleines Problem gibt es dabei allerdings. Bei Verwendung des Hooks würde ein Benutzer der die wp-login.php “normal” aufrufen würde (z.B. über ein Bookmark), ebenfalls das angepasste Stylesheet zu sehen bekommen. Es muss also ein Kriterium her anhand dessen man unterscheiden kann ob die wp-login.php im Iframe oder in einem normalen Fenster angezeigt wird. Im Script selber können wir das (leider) nicht überprüfen, deswegen geben wir diese Information beim Aufruf der wp-login.php als GET-Parameter mit: &iframe_mode=true sorgt dafür das wir im Hook überprüfen können ob dieser Parameter gesetzt ist. Ist dem so, dann wird das angepasste Stylesheet geladen, ansonsten passiert nichts.

function check_iframe_mode(){

	if( isset( $_REQUEST['iframe_mode'] ) && true === $_REQUEST['iframe_mode'] ){
		echo '<style type="text/css">
		@import url("' . site_url().'/themes/my_theme/my_login_style.css");
		</style>';
	}

}

add_action( 'login_head', 'check_iframe_mode' );

Das Stylesheet liegt in diesen Beispiel im Themes-Ordner ‘my_theme’ und hat den Namen ‘my_login_style.css’. Man kann das CSS auch direkt ausgeben, jedoch lässt sich eine separate Datei leichter pflegen und anpassen.

Fazit

Mit diesem letzten Schritt wären wir nun am Ziel angekommen. Nun findet der Login-Prozess komplett im Iframe statt. Allerdings hat diese Methode auch ein paar Nachteile die ich hier nicht verschweigen möchte.
Zum einen wird keine Rücksicht auf die Seite genommen in der das Iframe eingebettet ist. Sind hier Elemente vorhanden die vom Login abhängig sind, z.B. Anzeige des Benutzernamens, dann werden diese Elemente erst dann sichtbar bzw. aktualisiert wenn die Seite erneut geladen wird. Diesen Mangel kann man zur Not noch mit etwas zusätzlichem JavaScript beheben indem man einen Reload der Hauptseite erzwingt.
Zum anderen geht diese Methode nicht sehr schonend mit den Resourcen um. Denn alleine durch die Anzeige der wp-login.php werden Datenbankverbindungen aufgebaut, Variablen angelegt und damit letztendlich auch Speicher verbraucht. Für Blogs mit hohen Besucheraufkommen sollte man deswegen besser auf andere Lösungen, sprich komplexere Plugins, zurück greifen.
Für kleinere Blogs und den Hobby-Bastler jedoch eine Anregung für einen weiteren Ausbau. Ein Workaround könnte so aussehen, dass man nach dem Logout nicht direkt wieder auf die wp-login.php umleitet, sondern auf eine dritte “Zwischenseite” die einen Link zur wp-login.php enthält.

Ich hoffe dem einen oder anderem mit diesem Artikel ein paar Anregungen gegeben zu haben und freue mich über etwas Feedback in den Kommentaren.


1 Million mal

Hier im Blog ist es derzeit noch recht ruhig was arbeitsbedingtem Zeitmangel geschuldet ist. Dabei ist noch so einiges zu tun um das Blog fit zu bekommen. Kommt Zeit, kommt Blog.

Weniger ruhig geht es bei WordPress-Deutschland zu. Die verbuchten dieser Tage den 1 millionsten Download der DE-Version von WordPress. Alle Achtung, dass ist nicht gerade wenig.
Zu solch einem Jubiläum gibt es natürlich auch eine Kleinigkeit zu gewinnen. Einen USB-Stick mit 4GB Speicher, WP-Deutschland Gravur und Flaschenöffner. Ich hoffe ich muss nicht erwähnen das es mir bei der Teilnahme an dem Gewinnspiel weder um den 4GB USB-Stick noch um die Lasergravur geht, ich aber scharf auf den Flaschenöffner bin. Kann man immer gebaruchen, genauso wie ein funktionierendes WordPress.


WordPress und die Localization

Im Grunde genommen ist es eine feine Sache das man in WordPress Themes und Plugins mit relativ wenig Aufwand in eine andere Sprache übersetzen kann. Man muss lediglich darauf achten beim schreiben des Themes bzw. Plugins die Texte in einem bestimmten Format auszugeben und schon kann man unter zuhilfenahme von Programmen wie z.B. poEdit das Theme oder Plugin in eine andere Sprache übersetzen ohne es neu zu schreiben.

Im Grunde genommen bedeutet hier aber, dass es nur auf den ersten Blick so einfach zu sein scheint. Wer sich schon mal mit den Thema Übersetzung von Plugins oder Themes beschäftigt hat, der wird früher oder später die vielen kleinen Haken zu spüren bekommen. Einer dieser kleinen Haken ist der, dass WordPress einen grundlegenden Unterschied bei der Einbindung von Übersetzungen macht. load_theme_textdomain() ist, wie der Name vermuten lässt, für Themes zuständig, während load_plugin_textdomain() für Plugins zuständig ist. Es ist schon mal auffällig das es für die gleiche Aufgabe zwei verschiedene Funktionen gibt. Also schauen wir doch mal in die Core-Datei wo der Unterschied bei diesen beiden Funktionen liegt.

Beide Funktionen finden wir in wp-includes/l10n.php. Beide Funktionen erwarten bei ihren Aufruf die Übergabe einer Domain und eines Pfades. Im Falle von load_plugin_textdomain() können sogar zwei verschiedenen Arten von Pfaden angegeben werden, wobei der absolute relative Pfad veraltet ist und nicht mehr verwendet werden sollte.
Bei genauerer Betrachtung fällt aber auf, dass load_theme_textdomain() bei der Pfadangabe deutlich toleranter ist als load_plugin_textdomain(). Ist kein Pafd angegeben, so verwendet load_theme_textdomain() das Verzeichnis des aktuellen Templates als Pfad. Ansonsten könnte man den Pfad auch sonstwohin zeigen lassen, es würde noch funktionieren.
load_plugin_textdomain() ist da etwas strikter. Egal ob man einen Pfad angibt oder nicht, es wird auf jeden Fall WP_PLUGIN_DIR, also der Pfad zum Plugin-Verzeichnis, im Pfad eingefügt.
Mit load_theme_textdomain() könnte man also auch eine Sprachdatei benutzen die in wp-content steht. Mit load_plugin_textdomain() wäre dies unmöglich.

Nun sind Pfadangaben in WordPress eine Sache für sich an die man sich gewöhnen und mit denen man umzugehen lernen kann. Deutlich mehr Kopfschmerzen bereitete mir in der Vergangenheit ein Umstand, den ich irgendwie nicht verstehe. Und zwar das Format nach dem die Sprachdateien benannt werden müssen.
load_theme_textdomain() erwartet das die Sprachdatei lediglich aus der Locale und der Endung .mo besteht. Also z.B. de_DE.mo oder en_EN.mo. load_plugin_textdomain() hingegen erwartet zu der Locale noch zusätzlich die Domaine als Teil des Dateinamens. Die Datei muss also das Format domain-locale.mo haben.
Für mich recht unverständlich und letzten Endes kann dies auch zu Problemen führen. Denn ändert sich im Plugin die Domaine, z.B. nach einem Update, dann können auch die (alten) Sprachdateien nicht mehr geladen werden. Man müsste also entweder alle Domainen im Plugin ändern oder alle Sprachdateien umbennenen.

Aber man sollte auch immer die Vorteile im Auge behalten. Durch die Verwendung der Domaine im Dateinamen der Sprachdatei kann man auf recht einfache Weise z.B. zwischen der Du- und der Sie-Form wechseln. Etwas was für deutsche Übersetzungen ja nicht ganz unerheblich ist.
Dazu legt man einfach z.B. eine Sprachdatei mit den Namen pluginname_du_form-de_DE.mo und eine mit den Dateinamen pluginname_sie_form-de_DE.mo an. Im Plugin selber kann man mittels define('MeinePluginDomain', 'plugin_xx_form'); eine Konstante definieren die dann in den Übersetzungsfunktionen verwendet wird (z.B. __('This example text', MeinePluginDomain)). Je nachdem ob xx nun ‘du’ oder ‘sie’ ist, schaltet man zwischen Du- und Sie-Form um.

Jetzt hat man aber ein Problem mit Sprachen die nicht zwischen Du und Sie unterscheiden. Mit Englisch zum Beispiel. Denn man müsste nun für jede Sprache zwei Sprachdateien anfertigen. Einmal plugin_du_form-en_EN.mo und einmal plugin_sie_form-en_EN.mo.
Das ganze gestaltet sich an diesen Punkt als etwas zu aufwendig um schön zu sein. Es gibt aber auch eine Lösung für das Problem. Wir benötigen ja eigentlich nur eine Funktion die in einem Verzeichnis nachschaut ob es eine Sprachdatei nach dem Format domaine-locale.mo gibt und diese lädt. Gibt es eine solche Sprachdatei nicht, wird halt eine Sprachdatei nach den Format locale.mo geladen. Gibt es diese auch nicht, wird halt nichts übersetzt.
Nebenbei erledigt sich mit solch einer Funktion das leidige Pfad-Problem. Die Sprachdateien können nun quasi an einen beliebigen Ort abgelegt werden. Ob dies Sinn macht, sei an dieser Stelle mal dahin gestellt.

Hier erst einmal die Funktion:

function flexible_loadtextdomain( $path = false, $domain = 'default' ){

  if( ! $path || ! is_dir( $path ) )

    return false;

  global $l10n;

  $local = get_locale();

  $mofile = $path . $domain . '-' . $local . '.mo';

  // try to find a matching translation

  // first try domain-locale.mo

    if( ! file_exists( $mofile ) ){

    // if not found, try only locale.mo

    $mofile = str_replace( $domain . '-', '', $mofile );

      if( ! file_exists( $mofile ) ){

        // if even this failed, throw an error or do something wired

        return false;

      }

    }

  load_textdomain( $domain, $mofile );

  return isset( $l10n[ $domain ] );

}

Die Funktion erwartet zwei Parameter. Zum einen eine Pfadangabe und zum anderen den Namen der Domain. Die erste If-Abfrage sorgt dafür das überhaupt ein Pfad übergeben wird und das es auch ein Verzeichnis ist. Anstatt einfach nur false zurück zu geben, kann man hier natürlich noch weitere Spielereien unter bringen indem man z.B. einen Pfad vorgibt (WP_PLUGIN_DIR oder get_template_directory).
Der Rest ist das was ich oben schon beschrieben habe. Erst domain-locale.mo suchen, wenn nicht gefunden locale.mo suchen, wenn das auch nicht gefunden irgendwas verrücktes machen, zum Beispiel false zurück geben.
In der letzten Zeile wird dann noch geprüft ob die Domain erfolgreich geladen wurde. Ab WP 3.0 kann man dies durch return load_textdomain(...) abkürzen. In älteren Versionen muss man diesen kleinen Umweg gehen da load_textdomain keine Erfolgsmeldung zurück gibt.

Der Code der Funktion ist ein wenig abgespeckt. Will man es ganz sauber programmieren, dann würde man noch ein paar Sachen anders machen, z.B. nicht $locale = get_locale(); verwenden, sondern $locale = apply_filters( 'xyz', get_locale(), $domain ); (xyz = theme_locale oder plugin_locale, je nachdem ob man die Funktion in einem Theme oder Plugin verwendet).

Vielleicht ist dieser Beitrag ja eine kleine Anregung für alle die Themes oder Plugins schreiben.


Eigenes Menü im Backend anpassen

Eigene Menüs im Backend anlegen ist nicht schwer, WordPress stellt hierzu ausreichend Funktionen zur Verfügung. Jedoch haben diese Menüs einen meiner Ansicht nach unschönen Nachteil. Im Menü selber hat der erste Menüpunkt den gleichen Titel wie das Menü selber. Legt man nun also ein Menü Test an, so steht ganz oben als erster Menüpunkt auch Test (siehe Grafik). Das sieht nicht nur unschön, sondern auch unprofessionell aus. Schöner wäre es, könnte man den ersten Menüpunkt anders benennen. Bei den von WordPress vorgegebene Menüs ist es schließlich genauso.

Der Codex schweigt sich allerdings ein wenig darüber aus wie man es hin bekommt den ersten Menüpunkt anders zu benennen. Anscheinend gibt es auch keinen “offiziellen” Weg um dies zu bewerkstelligen. Mit einen kleinen Kniff geht es dennoch.
Dazu muss man die Variable $submenu global verfügbar machen und kann dann darüber seine Menüpunkte beeinflussen:

global $submenu;
add_menu_page( $page_title, $menu_title, $capability, $menu_slug, [...] );
$submenu[$menu_slug][0][0] = 'My first Menuoption';

$submenu ist ein Array in dem verschiedene Informationen über das Menü gespeichert sind. Es lohnt sich dieses Array mal ausgeben zu lassen und zu schauen welche Informationen man darin findet.

In Bezug auf die Menüs ist $menu ein weiteres interessantes Array. Bei der Masse an Plugins die im Umlauf sind, kommt es nicht selten vor das auch dementsprechend viele Plugins ein zusätzliches Menü einrichten. Da möchte man vielleicht sein eigenes Menü ein wenig von den anderen abgrenzen. Nichts leichter als das, sogar mit Zusatzwert. Man fügt einfach einen Menü-Seperator ein:

global $menu;
$menu[] = array( '', 'read', '', '', 'wp-menu-separator' );

Damit wird ein Seperator eingefügt mit dem man die Menüleiste auf- und zuklappen kann. Steht der Code vor add_menu_page(), wird der Seperator vor dem eigenen Menü eingefügt. Steht er dahinter, wird der Seperator dementsprechend dahinter eingefügt.
Mit count($menü) lässt sich recht einfach feststellen wie viele Menüs bereits existieren und ggf. ein Seperator einfügen. Allerdings werden auch sämtliche bereits vorhandenen Seperatoren mitgezählt, dies sollte man beachten und diese ggf. raus rechnen.
Mit 'wp-menu-separator-last' erzeugt man übrigens einen “leeren” Seperator, also einen Seperator ohne Pfeil wie am Ende des Standard-Menüs von WordPress zu finden ist.


Zahlen-Zahlen-Zahlen

Michael Kliewe hatte vor einiger Zeit erneut zu einem Wettbewerb aufgerufen. Diesmal galt es die Zahlen von 1 bis 10.000 als Zahlworte auszugeben. Relativ schnell kamen dann auch die ersten Lösungen rein die binnen 10-30 Minuten geschrieben worden sind. Ich als reiner Hobby-PHP’ler kann da nicht mithalten, dennoch hatte mich das Thema gereizt.

Vor gut 20 Jahren hatte ich nämlich genau dieses Problem, Zahlen als Wort ausschreiben, mal auf einen Amiga in Basic umgesetzt. Wenn ich mich richtig erinnere, war der Grund dafür irgendwelche Scheckeinreichungen wo der Betrag jeweils ausgeschrieben werden musste. Ich dachte mir, wenn ich es damals geschafft hatte, dann wird es diesmal wohl kein Problem sein. Die ersten drei Anläufe gingen schief, so einfach ist das Thema dann nämlich doch nicht. Dies wird auch deutlich, wenn man sich mal die bei Michael Kliewe geposteten Lösungen anschaut. Nicht wenige der Lösungen patzen bei den vielen kleinen und großen Stolperfallen die diese Aufgabe beinhaltet. Es wird z.B. tatsächlich “siebzehn” und nicht “siebenzehn” geschrieben, genauso wie es “siebzig” und nicht “siebenzig” geschrieben wird. Dafür schreibt sich die 77 jedoch “sieben-und-siebzig”. Dagegen war die Problematik das die Zahlen 13-19 ohne “und”, die Zahlen ab 21 jedoch mit “und” geschrieben werden eher harmlos.

Ich hatte die Anzahl an Sonderfällen auch stark unterschätzt, weswegen Versuch 1 und 2 mal glatt in die Hose gingen. Versuch 3 scheiterte in der Umsetzung des angeblich vorhandenen Systems welches hinter der Zahlenbenennung stecken soll.
Mittlerweile war der Wettbewerb quasi gelaufen, es gab viele gute und schlechte Lösungen und obendrein gab es bereits eine Zusammenfassung. Viel neues hätte ich somit nicht zu den Wettbewerb beitragen können, weshalb ich mich dazu entschied den Wettbewerb Wettbewerb sein zu lassen und nur für mich an dem Problem rumzuprokeln.

Beim vierten Versuch startete ich also mit einem komplett anderen Ansatz. Zum einen wollte ich die Lösung möglichst flexibel gestalten, sie sollte sich ohne großen Aufwand auch an andere Sprachen anpassen lassen (daran arbeite ich übrigens noch). Zum anderen war mir das gesteckte Ziel von 10.000 nun zu wenig. Warum sich mit 10.000 begnügen wenn ich auch eine Fantastilliarde umsetzen kann?
Fantastilliarden gibt es zwar nicht, jedoch Oktilliarden. Eine Oktilliarde ist eine 1 gefolgt von 51 Nullen. 999 Oktilliaraden ist bei meiner Lösung die derzeit größtmögliche darstellbare Zahl, übrigens eine Zahl mit 54 Stellen. Noch größere Zahlen sind theoretisch möglich, man müsste lediglich die Zahlnamen erweitern. Die Grenze dürfte bei einer Zentilliarde erreicht sein, dies ist eine 1 gefolgt von 603 Nullen. Das ist wohl weniger eine technische Grenze, sondern ich habe bisher keine Zahlnamen für noch größere Zahlen gefunden.

Stand der Dinge ist derzeit der, dass ich eine Klasse habe die eine 54 stellige Zahl in ein Zahlwort umwandeln kann. Die Klasse ist in den für mich üblichen Pidgin-PHP geschrieben. Sprich: Nicht schön, funktioniert aber.
Da ich dieses Weblog unter anderem deswegen eingerichtet habe um irgendwann mal von diesem Pidgin-PHP weg zu kommen, ist diese Klasse quasi ein längerfristiges Projekt für mich an dem ich wachsen und neue Erkenntnisse gewinnen will. Die Klasse wird also in Zukunft noch das eine oder andere mal Erwähnung finden. Vor allem aber werde ich den Code der Klasse weiterhin ständig optimieren und verbessern. Falls jemand trotzdem schon mal einen Blick auf den Code werfen will, bitte in den Kommentaren melden. Bisher ist es mir nämlich noch völlig unklar wie ich hier halbwegs vernünftig Code posten kann. Wenn denn überhaupt.

Wenn ich mich bei tumblr ein wenig eingewöhnt habe, werde ich auch noch ein paar Worte dazu schreiben was es mit dem Blog usw. auf sich hat.