Month: April 2016

Geschützte Metafelder

Ich fuchse mich mal wieder durch das WordPress System und bin heute auf eine interessante Funktion gestoßen: is_protected_meta(). Was macht diese Funktion und wozu ist sie gut?

Eigentlich ist es ganz einfach. Beginnt ein benutzerdefiniertes Feld mit einem Unterstrich, so gilt dieses Feld als “protected”, also geschützt. Dies bedeutet beispielsweise, dass ein solches benutzerdefiniertes Feld nicht durch den Template Tag the_meta() dargestellt würde und folglich nicht der Öffentlichkeit zugänglich gemacht werden soll. Darüber hinaus führt dies dazu, dass eine Abfrage, ob der aktuelle Nutzer das Feld aktualisieren kann, negativ beantwortet wird:


Dies kann man allerdings selbst ändern. Zum einen – falls man das benutzerdefinierte Feld zunächst mit register_meta() registriert – kann man als letzten Parameter eine Callback-Funktion übergeben, in welcher man selbst bestimmen kann, unter welchen Bedingungen ein Benutzer das Feld erstellen, aktualisieren oder löschen kann. Der zweite Weg geht über den Filter 'is_protected_meta', welcher neben dem zu filternden Boolean den Meta-Schlüssel sowie den Meta-Typen, also beispielsweise 'post' oder 'user' übergibt.

Geschützte benutzerdefinierte Felder können somit nicht über das WordPress Dashboard angelegt, editiert oder gelöscht werden. Sie tauchen auch nicht in der Metabox “Benutzerdefinierte Felder” auf. Dabei spielt es keine Rolle, welche Fähigkeiten der eingeloggte Benutzer besitzt, diese Felder sind nur von Plugins und Themes über Funktionen wie update_post_meta() und get_post_meta() erreichbar.

Warum Ajax über die WP REST API laufen lassen?

WP REST API

Die WP REST API hat in den letzten Monaten immer wieder für Wirbel gesorgt. Viel wurde geschrieben. Decoupled Themes, decoupled admins, mobile apps und jede Menge andere Schlagwörter fliegen durch die Luft. Ab sofort werden Themes total anders geschrieben werden, lernt REACT! Die einen “Yeah” die andern 😱
Na toll, da hat man sich gerade ein wenig in die WordPress Entwicklung reingefunden und gleich soll wieder alles anders werden?

Doch, wenn wir es mal kurz runterbrechen und uns mit einem ganz alltäglichen Beispiel der REST API annähern verschwinden viele der Sorgen. Ein großer Vorteil, den die WP REST API derzeit für jeden mitbringt: Sie bietet ein schnelleres Interface für Ajax Requests und das ist doch erstmal super.

Bisher haben wir Ajax Anfragen an die wp-admin/admin-ajax.php geschickt. Und – gelinde gesagt – das ist eine ziemlich langsame Methode. Dieser Weg war ursprünglich für ein paar Ajaxabfragen im Admin gedacht, blieb aber die einzige Schnittstelle und wurde deshalb auch von Plugins und so weiter für das Frontend genutzt.

Zwei Nachteile hat dieser Weg:

  • Manche sperren den wp-admin/-Ordner komplett via .htaccess. Ob gut oder schlecht, hilfreich oder nicht sei einmal dahingestellt. Doch dies bedeutet, dass Ajax-Requests hier so lange gegen die Wand laufen, bis man sich im Browser authentifiziert hat. Genau das sollen Frontend-Nutzer ja nicht; die Ajax-Anfragen funktionieren also nicht.
  • Die admin-ajax.php lädt den kompletten Admin, die ganzen Filter, die ganzen Hooks, alles mögliche, was wir für unsere Requests überhaupt nicht benötigen. Die Folge: Eine Ajax Abfrage, eigentlich bekannt für ihre Geschwindigkeit, lahmt.

In meinen Tests konnte ich für die gleiche Operation auf einer ziemlich leeren Seite (ein bißchen Content, BuddyPress und mein Plugin installiert) deutliche Ladezeit-Einsparungen erzielen:

Zehn Abfragen über admin-ajax.php
Zehn Abfragen über admin-ajax.php – 1,93 Sekunden

Zehn Abfragen über die REST API
Zehn Abfragen über die REST API – 1,25 Sekunden

Die REST API ist also ziemlich flott und die meisten Plugins lassen sich relativ schnell von einer Methode auf die andere umstellen. Dies mache ich gerade mit meinem BuddyPress Desktop Notifications Plugin, welches bald in Version 0.9 herauskommen wird und BuddyPress Nutzern Notifications anzeigt.

Sehen wir uns zunächst den bisherigen Code für den Ajax-Request an:

Wir registrieren die Ajax Action Hook, sammeln unsere Daten in der Variablen $entries, geben diese als JSON Objekt aus und lassen das Script sterben.

Die selbe Funktion über die REST API:

Erst einmal sieht es sehr viel mehr Code aus, das liegt aber vor allem an meinen Kommentaren, da ich mir vorgenommen habe, das Plugin langsam auch mal etwas schöner zu kommentieren. Schaut man sich die zentrale dn_query() an, sieht man, dass sich der Code kaum geändert hat. Nur gebe ich nun kein JSON-Objekt mehr mit echo aus und lasse das Script sterben sondern gebe die $entries zurück. Den Rest erledigt die REST API für mich.

Wir registrieren nun eine neue API REST Route im Action Hook rest_api_init. Dazu nutzen wir die Funktion register_rest_route(). Der erste Parameter gibt unseren “Namespace” an, den können wir frei wählen, aber ich glaube, hier werde ich einfach immer meinen Plugin Slug verwenden, um mögliche Konflikte zu vermeiden und danach geben ich im zweiten Parameter meine Route an. Aus diesen Angaben ergibt sich zum Schluss die URL zu diesem Endpunkt.

Hat das Script früher
http://localhost/wp-admin/admin-ajax.php

aufgerufen, so wird es jetzt
http://localhost/buddypress/wp-json/buddypress-desktop-notification/v1/notifications

aurufen müssen.

Danach übergebe ich zum einen Optionen-Array, in welchem die Methode festgelegt wird (in meinem Fall WP_REST_Server::READABLE was schlicht GET entspricht, die Callback-Funktion, meine gute alte dn_query(), sowie die erwarteten Argumente, also übergebenen Parameter. In meinem Fall since. Die darüber übergebenen Werte werden mit der in validate_callback hinterlegten Funktion dn_strtotime() validiert, so dass ich sicher sein kann, dass die Daten vor dem Aufruf von dn_query() valide sind. Schließlich, da nur eingeloggte Nutzer ihre Benachrichtigungen sehen sollen, lege ich im permission_callback die Funktion is_user_logged_in() fest.

Und das war es auch beinahe schon. Da ich bisher auch die admin-ajax.php über wp_localize_script() übergeben hatte musste ich hier die URL entsprechend nur austauschen:

esc_url_raw( rest_url() ) . 'buddypress-desktop-notification/v1/notifications'

Gibt mir die komplette URL zu meinem Endpoint zurück.

Darüber hinaus muss ich im Script ein Nonce übergeben, welches die Sicherheit der Ajax-Abfrage erhöht, so dass mein Localize-Array nun etwa so aussieht:

'ajax_url' => esc_url_raw( rest_url() ) . 'buddypress-desktop-notification/v1/notifications',
'nonce' => wp_create_nonce( 'wp_rest' ),

Auf Javascript-Seite sieht meine jQuery.ajax() nun folgendermaßen aus:

Entscheidend ist hier vor allem der beforeSend-Teil. Da die Notifications ja nur für eingeloggte Benutzer auf meiner Seite sichtbar sein sollen nutze ich die eingebaute COOKIE-Authentifizierung und muss zusätzlich noch das Nonce im Header 'X-WP_Nonce' mit übergeben.

Done und fast 50 Prozent schnellere Abfragen und dafür lohnt es sich allemal, sich ein wenig durch die Dokumentation der API zu wühlen. Ihr werdet schnell feststellen, das ist nicht der einzige Grund, warum sich das lohnt!