Umlaute, JQuery, Ajax, Formulare und PHP

Wir setzen im Rahmen unserer Web-Projekte neben dem serverseitigen PHP natürlich auch JQuery auf der Javascript-Seite ein - zunehmend auch, um in unsere CM-Applikationen ajax-basierte Features zu integrieren.

Gerne würden wir alles - Webseiten, JQuery/Ajax, PHP-Programme, MySQL etc. - stromlinienförmig auf UTF-8 ausrichten. Aber das geht leider nicht immer. Teils aus historischen Gründen, teils weil man es kundenseitig mit Apache-Webserver-Installationen zu tun bekommt, die per "AddDefaultCharset"-Directive auf ISO-8859-1 als Standard-Character-Set eingestellt sind, teils aber auch, weil man nicht beliebig an den vorgefundenen Datenbanken herumdrehen kann.

In diesem Umfeld stolpere ich dann ab und zu in das eine oder andere Problem mit Umlauten - zuletzt tatsächlich mal wieder im Zusammenhang mit den Ajax-Tools von JQuery. JQuery/Ajax kann in einem serverseitigen ggf. heterogenen Zeichensatz-Umfeld durchaus für Überraschungen sorgen. So sind im Internet haufenweise Fälle beschrieben, in denen eine bislang laufende PHP-Anwendung nach dem Einsatz von JQuery/Ajax z.B. deutsche Umlaute in den üblichen kryptischen Zeichensalat verwandelt hat, der bei einer ungewollten Zeichensatz-Konversion halt entsteht. Links zu Beispielen findet man weiter unten.

Nachfolgend deshalb ein paar Hinweise darauf, warum und wann man beim Einsatz von JQuery/Ajax aufpassen muss, und wie man mit den eventuell resultierenden Problemen umgehen kann. Auf Code-Beispiele habe ich verzichtet, da die Kenntnis der Problemursache einem Entwickler i.d.R. schon zeigt, was er zu tun hat. Ich hoffe, die Ausführungen sind trotzdem für den einen oder anderen, der sich mit JQuery, PHP und Umlauten herumschlagen muss, nützlich.

Ich rede nachfolgend übrigens nur über Systeme, in denen ISO-8859-1 bzw. Latin1-Zeichensätze sowie der UTF-8-Zeichensatz eine Rolle spielen.

Problemseparation: Klassische Umlautprobleme aufgrund von Webserver, PHP- und MySQL-Einstellungen

Es ist wichtig, JQuery/Ajax-induzierte Umlautprobleme von anderen Umlaut-Problemen bei Einsatz von PHP-Anwendungen abzutrennen.

Umlaute sind im PHP-, MySQL-Umfeld auch ohne JQuery von Haus aus ein Thema, das eine gewisse Komplexität aufweisen kann:

Dies liegt daran, dass neben Einstellungen des Webservers und neben der php.ini-Konfiguration natürlich auch die Zeichensatz-Einstellungen des Datenbankservers eine Rolle spielen.

Zeichensätze spielen grundsätzlich eine essentielle Rolle im Datenaustausch zwischen Client- und Serverkomponenten einer Applikation - also bei der Client/Server-Kommunikation. Hier gelten zwischen Web-Client und Web-Server Regeln und Konventionen, die man u.U. mit geeigneten Mitteln beeinflussen kann. Server und Client können sich im Rahmen bestimmter Protokolle in vielen Fällen auch darüber informieren, welchem Zeichensatz die eben ausgetauschten (Web-Daten) unterworfen sein sollen und wie die Daten dementsprechend vom Empfänger zu interpretieren und darzustellen sind.

In manchen Fällen gelten aber auch harte Regeln, die man dann schlicht kennen muss. Wie wir sehen werden, liefert JQuery/Ajax u.U. für letzteres ein wichtiges Beispiel.

(Hinweis zur Sicherheit: Ich spreche hier bei der Betrachtung der Web-Server-Client-Kommunikation nicht von URL-Encoding oder ähnlichem, sondern davon, welchem Zeichensatz die empfangenen Daten von der empfangenden Applikation letztlich zuzuordnen sind. Das hat mit URL-Encoding nichts zu tun.)

Klarmachen muss man sich ferner, dass auch viele Datenbanksysteme definierte oder einstellbare Zeichensätze in der Kommunikation und beim Datenaustausch mit ihren jeweiligen Clients einsetzen. Hierfür liefert MySQL ein schönes Beispiel (s.u.).

Andererseits gelten definierte Zeichensätze auch für die interne Repräsentation von Daten in einer Applikation und /oder einer Datenbank. Der Zeichensatz für die interne Datenrepräsentation oder die Ablage in einer Datenbank kann dabei vom Zeichensatz, den die Datenbank in der Kommunikation mit ihren Clients anwendet, durchaus abweichen! Für die korrekte Zeichenumsetzung sorgt die Bank selbst, man muss als Entwickler aber dennoch wissen, was für ein Zeichensatz für die Kommunikation gilt oder gelten soll und wie man das im Bedarfsfall beeinflussen kann.

Der Unterschied zwischen Zeichensätzen für die Datenbanktabellen und den Zeichensätzen für die Kommunikation mit den Datenbank-Clients führt im Zusammenspiel von MySQL und PHP oftmals zu erheblichen Verwirrungen. Man schaut auf die Kollation der Datenbank-Tabellen und vergisst leider oftmals, dass der Zeichensatz für den Transfer der Daten zwischen Datenbank und PHP-Applikation im Umfeld von Umlautproblemen viel entscheidender ist!

Als Konsequenz mussten auch wir immer wieder lernen, dass es sinnvoll und wichtig ist, die Datenbehandlung in den Basisklassen unseres hauseigenen Frameworks auf der PHP-Seite mit zeichensatzbezogenen Kodier- und Dekodier-Funktionen (unter Einsatz von z.B. "utf8_decode", "utf8_encode", "iconv" ) sowie MySQL-Anweisungen der Art

auszustatten - und zwar so, dass die Durchführung einer De- oder Enkodierung und/oder der jeweils zu beachtende Zeichensatz sich über Konfigurations- oder Setup-Parameter der Applikation steuern lassen. Damit kann man sich dann flexibel an die vorgefundene Server-Umgebung anpassen!

Dass man sich ohne Vorkehrungen und systematische Analyse der verschiedenen Server-Einstellungen sehr schnell in einem Gestrüpp wiederfinden kann, zeigen z.B. folgende Artikel:

http://www.php-resource.de/forum/php-developer-forum/100181-umlauteproblem-bei-formulardaten-ms-sql.html
http://www.sebastianviereck.de/de/mysql-php-umlaute-sonderzeichen-utf8-iso/
http://www.php.de/datenbanken/45101-problem-mit-utf8-latin1.html
http://www.winfuture-forum.de/index.php?showtopic=193063

Wie gesagt: Bei der MySQL-Datenbank muss man begrifflich sorgsam unterscheiden zwischen den "Collations"-Einstellungen für das Hinterlegen der Daten in den Datenbanktabellen und dem Zeichensatz, den die Bank in der Kommunikation mit ihren Clients (hier: PHP-Modulen des Webservers) einsetzt. Das sind verschiedene Dinge - vor allem eine fehlerhafte oder unerwartete Einstellung des Zeichensatzes für die Kommunikation mit DB-Clients ist nach meiner Erfahrung Ursache für viele Umlaut- und Zeichensatz-Probleme im PHP/MySQL-Umfeld.

Man führe sich im Falle des Falles nochmals den Artikel http://dev.mysql.com/doc/refman/5.1/de/charset-connection.html und die Möglichkeiten des Einsatz des "SET NAMES"-Kommandos bei der Initialisierung der Datenbankverbindung zu Gemüte! Ein kurzer Test mit "SET NAMES" hat mir schon oft geholfen - genau deswegen habe ich ja irgendwann eine generelle Möglichkeit zur Parametrierung in unseren PHP-Applikationssetup vorgesehen.

Bevor man JQuery-induzierte Umlaut-Probleme angeht, sollte man also wissen,

  • ob und mit welchen Zeichensätzen der Webserver und die Datenbank arbeiten und
  • auf Basis welchen Zeichensatzes jede dieser Serverkomponenten Informationen zu ihren jeweiligen Clients überträgt.

Insgesamt sollte man sich beim Analysieren von JQuery/Ajax-induzierten Umlaut-Problemen sicher sein, dass die eigene Applikation mit den unterschiedlichen Webserver- und Datenbankeinstellungen konsistent umgeht und dass nicht schon ohne JQuery ein Umlaut-Problem vorliegt.

Warum muss ich zusätzlich an JQuery denken?

Bei der Parametrierung, der Konfiguration und der Konzeption eigener PHP-Applikationssysteme tut man in jedem Fall gut daran, von vornherein an den Einsatz von JQuery/Ajax zu denken. Warum?

Weil JQuery und seine Ajax-Tools bestimmte Regeln für die Kommunikation mit Webservern befolgen, auf die man sich zumindest serverseitig einstellen muss ! Zu finden sind solche Regeln auf der Seite

http://api.jquery.com/jQuery.ajax/

Es lohnt sich, sich diese Seite in Gänze (!) zu Gemüte zu führen. Liest man dort vorschnell nur den nachfolgend zitierten Abschnitt zum "ContentType" beim Ajax-Setup, so kann man schnell Schiffbruch erleiden.

contentTypeString
Default: 'application/x-www-form-urlencoded; charset=UTF-8'
 
When sending data to the server, use this content type. Default is "application/x-www-form-urlencoded; charset=UTF-8", which is fine for most cases. If you explicitly pass in a content-type to $.ajax(), then it'll always be sent to the server (even if no data is sent). If no charset is specified, data will be transmitted to the server using the server's default charset; you must decode this appropriately on the server side.

Der letzte Satz deutet schon mal Schwierigkeiten an - man muss sich ggf. um die Servereinstellungen kümmern! Andererseits könnte man aber auch glauben, dass man den Zeichensatz für die Kommunikation mit dem Server clientseitig durch ein geeignetes Setup für die JQuery/Ajax-Transaktion vorgeben kann. Dies ist aber nicht durchgehend der Fall, denn es gilt auch folgendes weitere Zitat von der genannten api.jquery.com-Webseite:

Sending Data to the Server
 
By default, Ajax requests are sent using the GET HTTP method. If the POST method is required, the method can be specified by setting a value for the type option. This option affects how the contents of the data option are sent to the server. POST data will always be transmitted to the server using UTF-8 charset, per the W3C XMLHTTPRequest standard.

Das spielt natürlich eine essentielle Rolle, wenn man - wie wir - bei der Erstellung und dem Einsatz von CM-Systemen größere Nutzdaten aus umfangreichen Formularen per JQuery/Ajax zum Server übertragen muss. Eine solche Übertragung wird man typischerweise statt mit GET mit POST durchführen (Datenmenge, Sicherheit).

Anders als beim Datenaustausch über GET ist JQuery/Ajax im Falle eines Datenversands mit POST offenbar aus guten Gründen eigen und legt sich auf UTF-8 fest!

Blöd nur, wenn die serverseitige Zielumgebung konsistent auf ISO-8859-1 und nicht auf UTF-8 ausgelegt ist.

Sprich:
Du hast es mühsam geschafft, in einer konsistenten "ISO-8859-1/latin1"- Apache/MySQL-Kundenumgebung eine interaktive PHP-Applikation einwandfrei zum Laufen zu bringen. Dann schaltest du irgendwann nette JQuery/Ajax-Features auf POST-Basis dazu und bekommst möglicherweise und "überraschend" Zeichensalat in allen Texten.

Von solchen Erlebnissen zeugen viele Anfragen in Foren mit z.T. halbgaren Lösungsansätzen - ein paar Beispiele:

http://stackoverflow.com/questions/2539090/jquery-ajax-umlauts-special-characters-are-a-mess
http://forum.de.selfhtml.org/archiv/2009/7/t188450/
http://www.ajax-community.de/javascript/6113-jquery-formular-umlaute.html
http://www.ajax-community.de/javascript/8872-problem-umlauten.html
http://www.buildblog.de/2009/05/01/ajax-und-umlaute-das-ewig-wahrende-utf-8-problem-und-seine-losung/
http://www.php.de/javascript-ajax-und-mehr/71100-formular-umlaute.html
http://www.perl-community.de/bat/poard/thread/17021
http://www.fundstücke-im-netz.de/2012/03/28/umlaute-mit-ajax-verwenden/
http://ecotronics.ch.honorius.sui-inter.net/wordpress/2010/jquery-ajax-und-encoding/
http://de.softuses.com/68699
http://www.mail-archive.com/discuss@jquery.com/msg19364.html

Das ungewollte Erzeugen von Zeichensalat kann dabei selbst in Testumgebungen ein Problem werden, denn die nach dem Ajax-Einsatz durch kryptische Zeichen ersetzten Umlaute der deutschen, französischen oder skandinavischen Texte muss man auch da ggf. erstmal wieder aus Backups restaurieren. Wenn es sich um größere Texte handelt, wird ja keiner anfangen, das wieder von Hand zu bereinigen!

Was hilft beim JQuery/Ajax-Einsatz in eventuellen ISO-8859-1/Latin1-Umgebungen ?

Nachdem man einmal verinnerlicht hat, dass JQuery/Ajax bei POST als Datentransfermechanismus UTF-8 in der Kommunikation mit dem Webserver erzwingt, ist klar: Man muss serverseitig etwas tun, wenn in der dortigen Verarbeitungskette nicht UTF-8 vorgesehen ist.

Das Zauberwort - besser die einzusetzende Funktion - heißt dann:

utf8_decode().

Siehe hierzu

http://de.php.net/manual/de/function.utf8-decode.php

Spätestens an den Stellen, an denen die PHP-Module sich des übertragenen und im $_POST-Array gelandeten Inhalts annehmen (Prüfungen, Vorverarbeitung, etc.), muss man die per JQuery/Ajax übertragenen Strings in einer ISO-8859-Umgebung prophylaktisch mit "utf8_decode()" dekodieren!

Ob man eine pauschale initiale Decodierung für alle Strings im $_POST-Array vor allen weiteren Schritten vornehmen will, hängt ein wenig von den eingesetzten Analyse- und Prüfroutinen für die Daten ab ab. Das gilt im besonderen für sicherheitsbezogene Prüfungen - hier muss jeder selber wissen, was sinnvoll und richtig ist. Irgendwann im Zuge der Verarbeitung ist die Dekodierung unter den angenommenen Voraussetzungen aber unvermeidbar.

Aber:
Das gilt nur im Fall eines vorhergehenden Ajax/JQuery-Transfers auf ein auf ISO-8859-1 eingestelltes Zielsystem und genau genommen nur im Falle von Strings.

Wie weiss oder erfährt mein PHP-Programm darüber etwas?

Bzgl. der String-Eigenschaft:

Man kann z.B. is_string(), is_numeric(), etc. zum Testen einsetzen. Aus meiner Sicht sollte man aber sowieso durch andere Mechanismen bei der Übertragung von Formulardaten dafür gesorgt haben, dass das empfangende PHP-Programm sehr genau weiß, was es an welcher Stelle des POST-Arrays als Datentyp erwartet. Schon aus Sicherheits- und Validierungsgründen! Dieses Thema hat man also in der Regel von Haus aus schon im Griff.

Bzgl. der Kennzeichnung eines Ajax-Transfers für die PHP-Seite:

Ich benutze hierfür typischerweise ein wohldefiniertes zusätzliches "Hidden Input-Feld" im selben Web-Formular, dessen Nutz-Daten übertragenen werden sollen. Da ich ja eh' JQuery im Falle eines Ajax-Transfers bemühe, kann ich vor der Durchführung der POST-basierten Ajax-Transaktion dieses Input-Feld von JQuery auch noch schnell mit einem numerischen Wert füllen lassen, der dem PHP-Programm dann anzeigt, dass ein Ajax-Transfer vorliegt. Einfache numerische Werte sind von UTF-8 / ISO-8859-Transformationen ja nicht gefährdet (ASCII-Kompatibilität von UTF-8)!

Das serverseitige PHP-Programm kann aufgrund der übertragenen Zusatz-Information in einem speziellen, mit numerischem Inhalt belegten $_POST-Array-Feld herausbekommen, ob die POST-Daten per JQuery/Ajax-Transaktion geschickt wurden. Hat das PHP-Programm auf Basis seines parametrierten Grund-Setups nun noch Informationen darüber, ob es in einer UTF8-Server-Umgebung oder in einer ISO-8859/Latin1-Umgebung läuft, so ist klar, wann utf8_decode() einzusetzen ist und wann nicht.

Client- bzw. serverseitig läßt sich so ein informatives "Zusatz-Feature" natürlich sehr schön generalisiert und über entsprechende Methoden eigener Bibliotheksobjekte für Datentransaktionen abhandeln - sowohl unter Javascript als auch auf der PHP-Seite. Die benötigte Code-Menge ist wirklich minimal!

Hat man das erstmal codeseitig erledigt, kann man anschließend normale POST-Transfers mit Web-Seitenwechsel locker und entspannt mit Ajax-Transaktionen ohne Web-Seitenwechsel jederzeit kombinieren und zwischen beiden hin- und herwechseln.

Den geringen Aufwand, den man für die notwendigen Vorsorgemaßnamen client- und serverseitig investieren muss, spielt man danach locker wieder durch den bequemen, einfachen Einsatz der Ajax-Tools von JQuery ein.

Ohne jeden Zeichensalat! Zumindest in "ISO-8859-1"-Umgebungen. Mit anderen Zeichensätzen als ISO-8859-Varianten und UTF-8 bin ich nicht so vertraut, so dass ich das, was ich oben beschrieben habe, nicht auf andere Zeichensätze generalisieren möchte.

Viel Spaß nun mit Umlauten, JQuery/Ajax und PHP in UTF-8 und ISO-8859-Umgebungen!