WordPress' Ajax-Actions: Im Fehlerfall zu schweigsam

Neulich stand ich vor folgender Situation: Ein am Vortag gestartetes Produktivsystem eines Kundenprojektes zeigte den Fehler, dass Bilder im WordPress-Backend nicht mehr bearbeitet (zugeschnitten, gedreht, etc.) werden konnten. Das Problem zeigte sich nur auf diesem System, weder auf meinem Entwicklungssystem noch auf dem Testsystem. Da dem Projektstart eine relativ komplexe Migration voraus ging, war die Annahme schnell gefasst, die Ursache müsse etwas mit der Migration zu tun haben. Damit lag ich – wie sollte es auch anders sein – falsch. Das dumme an Annahmen ist, dass sie die Objektivität in der Wahrnehmung negativ beeinflussen. Wenn ich einen Fehler an einer bestimmten Stelle vermute, schau ich andernorts nicht so genau hin.

Das ganze hat mir im Nachhinein auch gezeigt, dass möglichst präzise Fehlermeldung und Fehlerbehandlung im Fall der Fälle viel Zeit sparen kann. Doch der Reihe nach…

Da es sich, wie gesagt, um ein Produktivsystem handelte, welches – wie so oft mit Verzögerung – erst einen Tag zuvor an den Start ging, wollte ich nicht alle Plugins eines nach dem anderen Deaktivieren um so den Fehler einzugrenzen. Die Error-Logs gaben auch nichts her obwohl alle Debug-Regler auf vollem Durchzug standen. Mit vi auf dem Server in den Core-Files herum zu editieren war mir aber auch zu heikel. Zum Glück gibt es sshfs:

$ sshfs user@remote.host:/var/www/ ~/remote/ 

Damit habe ich das Dateisystem des Servers in meinem lokalen Dateisystem gemounted und kann die Dateien immerhin mit der IDE bearbeiten.

 

edit-image-admin-ajax-request

Kein Bild im Media-Modal zu sehen.

Wie bereits erwähnt war das gemeldete Symptom: »Wir können keine Bilder mehr bearbeiten in WordPress. Zeigt ein Broken-Image.« Gemeint war das Media-Modal auf das man aus einem Beitrag heraus gelangt. Die URI des Bildes ist dabei nicht der Dateipfad sondern ein PHP-Skript: <img src="/wp-admin/admin-ajax.php&action=imgedit-preview&postid=34" />. Wie auf dem Screenshot zu sehen, passten weder die Response-Header »Content-Type« (text/html) noch »Content-Length« (2) zu den Erwartungen. Ich öffnete also die Datei /wp-admin/admin-ajax.php um zu sehen, was da vor sich ging:

  1. In admin-ajax.php ist zu lesen, dass die Action imgedit-preview zu den $core_actions gehört und die Funktion wp_ajax_imgedit_preview() aufruft, die

  2. in /wp-admin/includes/ajax-actions.php (Zeile 211) deklariert ist. Hier sind zwei direkte Ausstiegspunkte der Form wp_die( -1 ); möglich. (Plus zwei weitere in check_ajax_referer()). -1 war übrigens genau die Antwort auf den oben genannten Request /wp-admin/admin-ajax.php&action=imgedit-preview&postid=34.

    Um zu sehen, wo genau der Programmablauf »stirbt« änderte ich Zeile 221 von wp_die( -1 ); in wp_die( -2 ); was mir zeigte, dass stream_preview_image() einen falschen Wert zurück gab.

  3. Also weiter in /wp-admin/includes/image-edit.php (Zeile 533), zur Deklaration dieser Funktion. Hier wird an zwei Stellen ein WP_Error in ein FALSE als Rückgabewert umgemünzt und damit die Fehlermeldung faktisch verworfen. In meinem Fall nicht gerade hilfreich. Um den Ablauf weiter zurück zu verfolgen blieb mir nicht viel anders übrig als die einzelnen Rückgabewerte zu »dumpen«:

    // function stream_preview_image()
    $img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) );
    if ( '1.3.5.7' === $_SERVER[ 'REMOTE_ADDR' ] ) var_dump( $img );
    
    /*
    object(WP_Error)#691 (2) { 
    	["errors":"WP_Error":private]=> array(1) { 
    		["image_no_editor"]=> array(1) { 
    			[0]=> string(28) "No editor could be selected." 
    		} 
    	} 
    	["error_data":"WP_Error":private]=> array( 0 ) { 
    	} 
    }
    */

    Was zum …? Nach dem ich noch schnell den Dateipfad aus _load_image_to_edit_path( $post_id ) überprüft und für korrekt befunden hatte ging es weiter.

  4. Über wp_get_image_editor() und _wp_image_editor_choose() gelang ich schließlich zur Methode WP_Image_Editor_GD::test(). Deren erste Zeile sprang mich förmlich an.

    if ( ! extension_loaded('gd') || ! function_exists('gd_info') )
    	return false;

    In mir breitete sich schlagartig dieses zwiespältige Gefühl von Ernüchterung (»ist jetzt nicht wahr, oder?«) und Erleichterung (»zum Glück kein Patzer bei der Migration«) ein.

Debugging Log

Debugging Log

Ein Blick in phpinfo() bestätigte dann das Problem: die GD Library war nicht installiert. Sowohl bei dem Testsystem als auch dem Produktiv-Host handelt es sich um so neumodische VPS aus der Amazon-Welt, die ihre IPs häufiger wechseln als normale Menschen ihre Unterwäsche. Ich bat ausdrücklich darum das Produktivsystem mit der gleichen Konfiguration des Testsystems bereit zu stellen. Egal, sowas kann passieren es zeig aber, dass man bei jeder Fehlersuche immer alle Eventualitäten in Betracht ziehen sollte und sich nicht von Annahmen den Blick verstellen lassen.

Gesprächigere Ajax-Actions

Es zeigt aber noch etwas, wenn elementare Module fehlen und es die darauf aufbauenden Funktionen in das GUI schaffen (Media Modal), dann muss eine Fehlermeldung mehr aussagen als einfach nur -1!

Dabei sollte man immer den Sicherheitsaspekt im Hinterkopf behalten. Ajax-Actions die ohne Authentifizierung abrufbar sind, sollten keine Fehlermeldungen ausgeben, die Rückschlüsse auf die Systemkonfiguration geben, klar. Aber in diesem Fall war der Benutzer Authentifiziert und WP_DEBUG auf TRUE. Wenigstens die Nachricht des WP_Errors hätte es in die Ausgabe schaffen sollen.

 Unmount

Beinahe hätte ich es vergessen. Das Dateisystem des Servers sollte man anschließend auch wieder außhängen:

$ fusermound -u ~/remote/

Kommentare

Es wurden noch keine Kommentarte zu diesem Artikel geschrieben.

Fragen, Ideen oder Kritik? – Hier ist Platz dafür!

Dein Kommentar

Um ein Kommentar abzugeben, reicht der Text im Kommentarfeld. Die Angabe eines Namens wäre nett, ist aber nicht erforderlich.

Du darfst folgenden HTML-Code verwenden, musst aber nicht:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>