Month: May 2015

wp_remote_get() vs. wp_safe_remote_get()

Über eine kleine Twitter-Diskussion, welche sich an einen Tweet von Pippin anschloß wurde ich nochmals darauf aufmerksam, dass die HTTP API von WordPress auch über “safe” Funktionen verfügt. Ich hatte irgendwann die letzten Wochen schonmal davon gehört, war der Sache aber zunächst nicht weiter nachgegangen. Es geht um folgendes: Die Funktionen wp_remote_post(), wp_remote_get() und wp_remote_head() verfügen über Geschwister, die allerdings im Codex nicht näher erläutert werden, sondern nur in der Referenz aufgeführt werden:

Nach den vergangenen Diskussionen zum Thema Sicherheit schrillen natürlich die Alarmglocken, wenn plötzlich “safe”-Funktionen auftauchen, von denen man nicht genau weiß, wozu sie dienen. Worin besteht also der Unterschied zwischen wp_remote_post() und wp_safe_remote_post()?

Wie die HTTP API von WordPress funktioniert

Klären wir zum Einstieg schnell nochmal, was die HTTP API eigentlich ist und wie sie funktioniert. In wp-includes/http.php finden sich verschiedene Funktionen, mit welchen man HTTP Requests absetzen kann. Alle diese Funktionen basieren letztlich auf der WP_Http() Klasse, die sich in wp-includes/class-http.php findet.

Mit Hilfe von wp_remote_post() kann man also relativ einfach einen POST Request absetzen und die erhaltenen Daten weiterverarbeiten. Ein Tutorial, wie man dabei vorgeht hat Tom McFarlin auf Tuts+ veröffentlicht.

Wie unterscheiden sich nun wp_remote_post() und wp_safe_remote_post()?

Liest man sich in den Quellcode ein, so findet sich eigentlich nur ein Unterschied: wp_safe_remote_post() – und das gilt stellvertretend für alle safe_remote-Funktionen – übergibt im Argumenten-Array den Boolean 'reject_unsafe_urls' mit true:

Wie Ihr seht, werden die Argumente dann an die WP_Http() Klasse übergeben. Zu was führt dort also 'reject_unsafe_urls'? Wenn dieser Boolean true ist, wird die URL mit der Funktion wp_http_validate_url() validiert.

Wozu dient wp_http_validate_url()?

wp_http_validate_url() hielt mit WordPress 3.5.2 Einzug in den Core. Blickt man in die Beschreibung des Quelltextes, so dient die Funktion wp_http_validate_url() dazu, URLs für die sichere Verwendung in der HTTP API zu verwenden.

Validate a URL for safe use in the HTTP API.

Wie geht die Funktion vor? Zunächst wird mit Hilfe von wp_kses_bad_protocol() geprüft, ob das Protokoll der URL gültig ist. Gültige Protokolle sind hierbei ausschließlich HTTP und HTTPS. Ohne den Parameter 'reject_unsafe_urls' wäre auch SSL ein gültiges Protokoll (s. wp-includes/class-http.php L186). Sollte es sich um kein gültiges Protokoll handeln wird false zurückgegeben.

Als unsicher gilt eine URL auch dann, wenn diese einen Benutzernamen oder ein Passwort enthält, um sich zu authentifizieren. Folgende URL würde also zurückgewiesen: http://benutzername:passwort@example.com

Enthält der Host eines der folgenden Zeichen, wird die URL zurückgewiesen: :#?[]

Auch externe URLS, welche nicht an die Ports 80, 443 oder 8080 – also die gewöhnlichen HTTP-Ports verweisen, werden zurückgewiesen.

Wenn die URL identisch mit der Home URL ist, der Port allerdings ein anderer als 80, 443 oder 8080 ist, wird die URL zurückgewiesen.

Und das bringt mehr Sicherheit?

Um die Entstehungsgeschichte der safe_remote-Funktionen zu verstehen ist es hilfreich, das Ticket 24646 zu studieren. In der WordPress Version 3.5.2 konzentrierten sich die Core Entwickler darauf, die HTTP API gegen Server Side Request Forgeries abzusichern. Nehmen wir einmal an, die HTTP API würde die Protokolle der URL und ähnliches keiner weiteren Prüfung unterziehen und einfach den angeforderten Request ausführen. Die angefragte URL wäre nun dict://locahost:11211/stat. Dies würde nun den String “stat” an den lokalen Memcached schicken, welches für gewöhnlich den Port 11211 abhört. Da der Aufruf lokal erfolgt greift auch keine Firewall oder ähnliches, um die Abfrage zu verhindern. Um es knapp zu halten: Danger! In 3.5.2 versuchte man deshalb dererlei Angriffe abzuwehren, sprang dabei allerdings über das Ziel hinaus. So konnten überhaupt keine Zugriffe mehr auf die lokale Installation erfolgen. Wenn man beispielsweise über wp_remote_get() das eigene Feed fetchen wollte, um es darzustellen, war dies nicht mehr möglich.

Deshalb wurden einige Schranken wieder abgebaut (der obige Angriff wird allerdings nach wie vor auch mit wp_remote_get() unterbunden) und die sicheren remote-Funktionen eingeführt, welche den Standard hielten. Solange man sicher sein kann, dass die URL keinen Angriffsvektor auf das eigene System bilden kann, kann man damit weiterhin auf wp_remote_get(), wp_remote_post() und wp_remote_head() zurückgreifen. Das macht vor allem dann Sinn, wenn man Requests auf den eigenen Host unternehmen möchte.

Kommt die URL hingegen aus einer unsicheren Quelle dienen wp_safe_remote_get(),wp_safe_remote_post() und wp_safe_remote_head() dazu, den Sicherheitslevel noch einmal zu erhöhen. Von nun an kann auf den eigenen Host nur noch begrenzt zugegriffen werden, nämlich unter den oben genannten Bedingungen.

TLTR

Wenn Ihr nicht sicher seid, welche URL ihr letztlich über wp_remote_get() aufruft, ruft sie über wp_safe_remote_post() auf. Das ist schlicht sicherer.

Was tun, wenn ein Elternmenüpunkt nicht verlinkt sein soll?

Ich surfe gerade durch den Blog von Perun als mir auffällt, es wäre auch für mich mal wieder an der Zeit einen kleinen Beitrag zu schreiben. Dabei stoße ich auf einen Beitrag mit einem eher seltsamen Titel: “Nicht anklickbare Eltern im Navigationsmenü erstellen“. Thordis erklärt dort die Möglichkeit, wie man einen Elternmenüpunkt erstellt, welcher jedoch selbst zu keiner Seite führt.

Im Wesentlichen läuft es darauf hinaus, dass man diesen Elternmenüpunkt in WordPress als Link anlegt und die Link-URL letzten Endes leer lässt. Eigentlich ist das eine sehr nette Idee und ich dachte, ich mache Euch hier kurz auch auf diesen Beitrag aufmerksam.

Woomattic! Automattic übernimmt WooCommerce

Die Nachricht schlägt ein wie eine Bombe! Automattic, die Firma, welche sich hinter wordpress.com verbirgt und von dem WordPress Mitbegründer Matt Mullenweg gegründet wurde, erklärt man werde WooCommerce übernehmen. In einem Youtube-Video erklärt Matt seine Intentionen:

Bisher weiß ich noch nicht genau, was diese Meldung bedeutet und jeder ist erst einmal ein wenig am rätseln, was als nächstes passieren wird. Deshalb stelle ich an dieser Stelle keine weiteren Mutmaßungen an, sondern verlinke die Beiträge, welche ich so gefunden habe:

Auch sehr interessant ist der Anfang des Podcasts WPWeekly zu dem Thema. Jeff Chandler und Marcus Crouch diskutieren hier die Auswirkungen dieser Aquisition auch für andere Plugins wie EDD und ich welche Richtung sich WooCommerce nun entwickeln könnte.

Man wird sehen wie sich diese Geschichte entwickelt. Stay tuned 🙂

Unterstützt das Theme mein Plugin?

Es ist gute Praxis, Funktionalität und Layout in WordPress auseinanderzuhalten und deshalb auf der einen Seite Plugins einzusetzen und auf der anderen Themes. Doch die Abgrenzung ist nicht immer scharf zu ziehen. Ein Beispiel wäre, wenn mein Plugin einen eigenen Posttypen entwickelt, welches sich möglichst nahtlos in das Theme einpassen sollte. Doch eine single.php für den neuen Posttype sollte dann entsprechend im Theme vorhanden sein.

So prüft beispielsweise WooCommerce, ob das aktuelle Theme das Plugin unterstützt oder nicht. Wenn das Theme keine Unterstützung deklariert gibt WooCommerce einen entsprechende Meldung im Admin aus. Soll sich dann niemand wundern, dass die Produktpräsentation nicht so aussieht, wie man sich das gewünscht hätte.

WooCommerce prüft, ob das Theme mit dem Plugin kompatibel ist
WooCommerce prüft, ob das Theme mit dem Plugin kompatibel ist

In diesem Beitrag werde ich Schritt für Schritt darstellen, wie man selbst eine solche Überprüfung durchführen kann und entsprechend eine Benachrichtigung im Admin hinterlässt.

Erster Schritt: Das Theme

Zunächst müssen wir uns natürlich fragen, wie ein Theme signalisiert, dass es das entsprechende Plugin unterstützt,beispielsweise indem es eine entsprechende single-{posttype}.php zur Verfügung stellt. WordPress stellt seit Version 2.9 mit add_theme_support() (zu finden in wp-includes/theme.php) eine praktische Schnittstelle bereit, welche wir dazu nutzen möchten. Die Funktion akzeptiert zwei Parameter, das entsprechende Feature, welches unterstützt werden soll, sowie weitere Argumente. Wir konzentrieren uns hier nur auf den ersten Parameter.

Normalerweise unterstützen Themes Beitragsbilder (post-thumbnail) oder benutzerdefinierte Hintergründe im Customizer (custom-background). Doch auch wir können uns einfach ein Feature überlegen und der Themeautor kann mit add_theme_support() dieses dann unterstützen. Sagen wir unser Feature heißt “plugin_theme_support“, so könnte der Themeautor nun mit folgendem Code die Unterstützung für dieses Feature signalisieren:

Zweiter Schritt: Das Plugin prüft

Im zweiten Schritt müssen wir in unserem Plugin nun natürlich überprüfen, ob das aktuelle Theme das Feature “plugin_theme_support” unterstützt. Ebenfalls seit Version 2.9 gibt es dazu die Funktion current_theme_supports(). Auch diese Funktion nimmt als ersten Parameter den Featurenamen und als zweiten optional weitere Argumente auf. Bleiben wir in unserem Beispiel zunächst einfach beim Namen. Wichtig ist zu bedenken, dass wir diese Abfrage erst sinnvoll durchführen können, wenn das Theme seine Unterstützung schon deklariert hat. Dies geschieht normalerweise im Actionhook after_setup_theme.

Am Ende möchten wir einen Admin Notice haben, welcher anzeigt, dass das aktuelle Theme unser Plugin nicht unterstützt. Diesen wollen wir allerdings auch ausblenden können, so dass er den Admin nicht mit jedem Page Load stört. Dazu hinterlegen wir uns eine Option, ob der Administrator den Hinweis angezeigt bekommen möchte. Über einen Button kann er dann den Hinweis abstellen. Während wir prüfen, ob das aktuelle Theme das Plugin unterstützt prüfen wir zugleich, ob ein entsprechender Hinweis dargestellt werden soll. In einer letzten Aktion möchten wir, sollte der Hinweis abgestellt sein, diesen wieder aktivieren, wenn der Administrator das Theme wechselt, so dass er zumindest darauf hingewiesen wird, dass auch das neue Theme nicht mit unserem Plugin kompatibel ist.

Die Prüfung vornehmen

Die Prüfung nehmen wir in dem Actionhook admin_print_styles vor. Wir könnten auch einen anderen nehmen, solange er vor admin_notices ausgeführt wird. Mit get_option() ziehen wir uns zunächst die Information, ob der Hinweis angezeigt werden soll. current_theme_supports( 'plugin_theme_support' ) dient uns dazu, herauszufinden, ob unser Plugin unterstützt wird. Sollte das Plugin keine Unterstützung finden und der Hinweis angzeigt werden, klinken wir uns in den Actionhook admin_notices ein.

Den Hinweis anzeigen

Der admin_notices ist ausschließlich dafür verantwortlich, Hinweise im Admin darzustellen. Deshalb geben wir hier einfach nur unseren HTML-Code aus, welcher im Notice dargestellt werden soll.

Einzig interessant hier ist unser Button, welchen wir einbinden. Mit Hilfe des GET-Parameters hide_theme_support_notice möchten wir später abfragen, ob der Administrator auf den “Hinweis verbergen”-Knopf gedrückt hat. Wir verwenden zur Konstruktion der URL dabei die Funktion add_query_arg(), welche die aktuelle URL um einen weiteren Parameter erweitert. Die Ausgabe von add_query_arg() escapen wir dabei unter zuhilfenahme von esc_url und verhindern damit eine mögliche XSS Attacke.

Unser Admin Notice im WordPress Dashboard
Unser Admin Notice im WordPress Dashboard

Den Hinweis verstecken

Nun müssen wir die Funktionalität des Buttons herstellen, damit der Hinweis auch tatsächlich versteckt wird. Auch hierzu klinken wir uns in die admin_print_styles, könnten aber auch einen anderen Hook nutzen. Wir prüfen einfach, ob der GET-Parameter entsprechend übergeben wurde. Ist dem so, setzen wir unsere Option auf den Wert 1.

Nur für den Spannungsbogen diesen Beitrags habe ich diesen Code in eine neue Funktion gepackt. Natürlich würde man ihn besser in unserer ersten Funktion plugin_theme_support() mit verarbeiten. Wenn man ihn allerdings in eine zweite Funktion packt, sollte man darauf achten, dass diese vor plugin_theme_support() ausgeführt wird.

Den Notice wieder darstellen, wenn das Theme wechselt

Sollte das Theme nun gewechselt werden, möchten wir – falls nötig – die Meldung wieder ausgeben. Dazu klinken wir uns in den Actionhook switch_theme ein. Diese Aktion wird ausgeführt, wenn das Theme gewechselt wird. In dieser setzen wir unsere Option plugin_theme_support also schlicht zurück.

Schluss

Mit diesen simplen Schritten kann man also selbst eine eigene Prüfung vornehmen, ob das aktuelle Theme das eigene Plugin unterstützt und wenn dem nicht so ist kann man im Dashboard einen Hinweis hinterlassen, welchen der Administrator aber auch abstellen kann.

Hat wp_is_mobile() noch irgendeinen Nutzen in Zeiten von responsivem Layout?

Heute möchte ich einen kurzen Blick auf eine Funktion in WordPress werfen, welche manche vielleicht für veraltet halten: wp_is_mobile() (zu finden in wp-includes/vars.php). Anhand des Browser User Agents stellt diese Funktion fest, ob die Seite von einem mobilen Endgerät abgerufen wird oder nicht. Was? Und das in Zeiten von responsivem Design? Fünf Jahre nach Ethan Marcottes legendärem Artikel über Responsives Webdesign?

Verwendung im WordPress Code

Und trotzdem, gibt es nicht Einsätze, bei welchen wir auf PHP Ebene wissen müssen, ob der Nutzer mobil unterwegs ist? Schauen wir uns zunächst an, wo wp_is_mobile() im WordPress Core Verwendung findet.

In der Adminbar

Wenn die Adminbar gerendert wird, wird mit Hilfe von wp_is_mobile() geprüft, ob das Endgerät mobil ist. Sollte dies der Fall sein, wird die Leiste um die Klasse .mobile erweitert (siehe wp-includes/class-wp-admin-bar.php), wobei, wenn ich das richtig sehe, es zu dieser Klasse keine weiteren Spezifikationen gibt. Auch der body-Tag wird des Admins um die Klasse .mobile erweitert, was Auswirkungen auf die Menünavigation hat.

Der Editor

Der WordPress Editor in der mobilen Ansicht
Der WordPress Editor in der mobilen Ansicht

Wenn man einen Blogpost verfasst, schreibt man diesen für gewöhnlich im WYSIWYG Modus. Zumindest hat man die Möglichkeit dazu. Jedoch nicht, wenn man mobil unterwegs ist. In diesem Fall wird die Funktion user_can_richedit() (wp-includes/general-template.php) false zurückgeben und nur noch der “Text”-Modus übrigbleiben. Der “Visual”-Tab, welchen man oben rechts findet verschwindet genauso wie der Vollbild-Knopf.

Weitere Einsätze

Desweiteren findet die Funktion noch Einsatz in Funktionen wir _device_can_upload(). Hier ist sie allerdings von keiner entscheidenden Funktion. WordPress nutzt den Boolean auch, um ihn beispielsweise an Javascript weiterzugeben. So beispielsweise im Customizer.

Wie funktioniert wp_is_mobile() genau?

Das Prinzip ist relativ simpel. Es wird geprüft ob der Useragent des Browsers einen der folgenden Textstrings enthält: “Mobile”, “Android”, “Silk/”, “Kindle”, “BlackBerry”, “Opera Mini” oder “Opera Mobi”. Sollte dem so sein wird true zurückgegeben, andernfalls false. Mit diesen sieben Teilstücken von Useragents sind alle wesentlichen mobilen Browser erfasst, was diese Funktion relativ treffsicher agieren lässt. Natürlich kann es immer sein, dass ein Nutzer seinen Useragent händisch ändert und damit durch das Raster fällt, doch übersieht man vereinzelte Grenzfälle ist diese Funktion relativ sicher im Aufspüren, ob das Endgerät mobil ist.

Weitere interessante Anwendungsfälle

Gerade mit dem letzten Mobile-Update von Google ist das Thema natürlich noch einmal in den Fokus gerückt. Mobile Geräte rendern Webseiten generell sehr viel langsamer als Desktopgeräte. Häufig haben sie auch eine schlechtere Internetverbindung. Das alles geht zu Lasten der User Experience. Wenn man nun vor allem für Desktop entwickelt, schwergewichtige Slider über die Webseite jagt und vieles mehr, so bedeutet dies für mobile Nutzer eine mittlere Katastrophe. Nicht nur sind Slider häufig dann doch nicht so mobil, wie man glaubt, es sind vor allem die Ladezeiten, welche hier häufig zu hohen Ausstiegsraten führen. Warum also nicht die Ausgabe von Slidern und anderen hochauflösenden aber vielleicht nebensächlichen Features mit wp_is_mobile() auf mobilen Geräten verhindern?

Oder man könnte eine schöne Zusatzfunktion einbauen, wenn der Benutzer mobil unterwegs ist. Häufig werden am Kopf der Seite ja Kontaktdaten wie Telefonnummer und dergleichen untergebracht. Wieso nicht im wp_is_mobile() prüfen, ob es sich um ein mobiles Gerät handelt und daraufhin einen SMS-Button unterbringen?

Fazit

Man sieht, wie selbst der WordPress Core aus einer Funktion, welche zunächst ein wenig veraltet wirkt, noch Nutzen zieht und auch die zwei kleinen Anwendungsbeispiele, welche ich präsentiere wären meines Erachtens besser schon bei der PHP Ausgabe ein- oder ausgeblendet, statt sie später mit CSS auf display:none; zu setzen.

Wenn Euch weitere Verwendungszwecke für die Funktion einfallen, oder wenn Ihr anderer Ansicht seit, ich freue mich immer über Kommentare. 🙂