Probleme mit ShortCodes

Seid WordPress 2.5 bietet das Blogging-System seinen Erweiterungen (Plugins) die neue Möglichkeit an,  mit so genannten ShortCodes Inhalte einfach und dynamisch zu modifizieren. Dazu muss eine Modifikations-Funktion auf einen entsprechenden ShortCode registriert werden, welcher dann automatisch aufgerufen wird. Ein ShortCode kann dabei noch zusätzliche Parameter definieren, die dann dieser Funktion zur Auswertung durchgereicht werden (Beispiel mit zwei Parameter: [ShortCode Param1=Wert1 Param2=Wert2]). Eines der berühmtesten WordPress-Erweiterungen, welche in der neusten Version diese Technologie einsetzt, ist die Gallery-Verwaltung NextGEN Gallery. Und mit ihrem Einsatz fällt ein kleiner Fehler in WordPress auf: ShortCodes kombiniert mit sehr langen Beiträgen führen bei der Anzeige gerne zu einem leeren Ergebnis.

Dies sieht sehr verdächtig nach einem Speicher-Problem aus. Doch wo genau tritt der Fehler auf und was kann man dagegen unternehmen? Ein kleiner Suchtrip durch den Quellcode von WordPress (zum Zeitpunkt dieses Artikels in der Version 2.7 ) zeigte bald, dass der Inhalt einer meiner langen Artikel in der Funktion wpautop() in der Datei ./wp-includes/formatting.php verloren ging. Und zwar genau in der vorletzte Zeile der Funktion:

$pee = preg_replace('/<p>s*?(' . get_shortcode_regex() . ')s*</p>/s', '$1', $pee); // don't auto-p wrap shortcodes that stand alone

(WordPress 2.7, Datei formatting.php, Funktion wpautop(), Zeile 153)
Diese Funktion sorgt dafür, dass vor der Ausgabe der Beiträge Zeilenumbrüche in HTML-Zeilenumbrüche (<br />) und HTML-Paragraphen (<p />) umgewandelt werden. Sie wird vor der Ersetzung der ShortCodes aufgerufen. Die letzte Ersetzung sucht nun allein stehende ShortCodes und entfernt mögliche (automatisch hinzugefügte) Paragraphen um diese. Die hier aufgerufene Methode get_shortcode_regex() liefert dabei das Suchmuster für die definierten ShortCodes zurück. Diese befindet sich in der Datei ./wp-includes/shortcodes.php ab der Zeile 165. Der hier zurück gelieferte reguläre Ausdruck sucht nach einem Muster in der folgenden Form:

ShortCodes: Problematischer RegEx

ShortCodes: Problematischer RegEx

In diesem Ausdruck werden zwei nicht-gierige (non-greedy, lazy) Quantoren eingesetzt. Dies bedeutet, es wird sehr viel Backtracking betrieben: die RegEx-Engine überspringt im ersten Schritt die Zeichen, welche auf solche nicht-gierigen Quantoren passen, da ja an dieser Stelle versucht wird, so wenig wie möglich einzufangen. Trotzdem merkt sich die Engine, wo sie notfalls nachschauen muss, falls der ganze restliche Text so erstmal nicht in das Suchmuster passt. Der „ganze Rest“ bei großen Beiträgen mit mehreren nicht-gierigen Quantoren kann ganz schön viel sein, was die Historie gerne anwachsen lässt. Wir dürfen nicht vergessen: die RegEx-Engine geht am Ende nach und nach wieder ein Schritt zurück und prüft erneut nach, ob sie ein Ergebnis findet (Backtracking). Um den Aufwand zu minimieren, bietet es sich an, den zu überprüfenden „Rest“ lokal einzuschränken. In unserem Fall wird der Zeichenbereich der Parameterpaare mit dem Endzeichen „]“ eines Tags begrenzt. Ändern wir nun den roten Abschnitt (.*?) zu ([^]]*?), trennen wir somit die Non-Greedy-Bereiche (Parameterpaare und mögliche Zwischeninhalte) und minimieren den Aufwand und somit den Speicherverbrauch. Unser überlanger Artikel müsste wieder erscheinen.
Mit einer kleine Schönheitsoperation können wir noch die (in meinen Augen) unnütze grüne, nicht-einfangende Klammerung um den eingefangenen optionale Schrägstrich entfernen: (?:(/))? zu (/)?. Die modifizierte Methode get_shortcode_regex() sieht nun wie folgt aus:

function get_shortcode_regex() {
    global $shortcode_tags;
    $tagnames = array_keys($shortcode_tags);
    $tagregexp = join( '|', array_map('preg_quote', $tagnames) );
    return '[('.$tagregexp.')b([^]]*?)(/)?](?:(.+?)[/1])?';
}

(WordPress 2.7, Funktion get_shortcode_regex(), Zeile 165 in Datei shortcodes.php)
(Problems in WordPress with long posts and plugins like NextGEN Gallery)

7

Kommentare

  1. smilkobuta  Januar 25, 2009

    Very Thanks to you!!!
    I fi­nal­ly un­ders­tood the source of this pro­blem and pro­blem its­elf.
    I can’t read German but your com­ment image is very ea­sy to un­der­stand.
    Danke schön!!

    antworten
  2. smilkobuta  Januar 25, 2009

    I re­port your code to WordPress Trac
    http://trac.wordpress.org/ticket/8962

    antworten
  3. Matthias Brusdeylins  Januar 27, 2009

    Thanks for re­porting this bug to WP Trac. I ho­pe they un­der­stand, that they have to di­vi­de the two Non-Greedy-Char-Areas (in our examp­le on the first squa­re bra­cket „]“ – red text). So the en­gi­ne needs less back­trackings and in this ca­se less me­mo­ry…
    that’s all… 🙂

    antworten
  4. Matthias Brusdeylins  Juni 16, 2009

    Auch in WordPress 2.8 ist der Fehler wei­ter­hin vor­han­den.
    Hier liegt der Speicher-Fresser eben­falls in der nicht-gierigen Klammerung. Die letz­te Zeile in der Funktion get_shortcode_regex() lau­tet hier nun kor­ri­giert:
    re­turn '(.?)[('.$tagregexp.')b([^]]*?)(/)?](?:(.+?)[/2])?(.?)';
    Das Ticket ist wei­ter­hin of­fen:
    http://core.trac.wordpress.org/ticket/8553
    WP 2.9 soll es rich­ten… da­bei ist es so­oo ein­fach…

    antworten
  5. Matthias Brusdeylins  November 21, 2009

    In WordPress 2.8.6 be­steht das Problem wei­ter­hin. Der Fix oben (Austausch der RegEx-Zeile) geht aber auch hier und muss nach dem Update wie­der durch­ge­führt wer­den…

    antworten
  6. Matthias Brusdeylins  Dezember 23, 2009

    Mit WordPress 2.9 ist die­ses Problem noch grö­ßer ge­wor­den. Der hier be­schrei­bene Fix reicht wohl nicht mehr aus. Da un­ter WP 2.9 auch mein Theme nicht mehr funk­tio­niert (war­um auch im­mer ?!), wer­de ich mich die­sem Thema wohl erst ab WP 3.0 an­neh­men. Dann mit ei­nem kom­plett über­ar­bei­te­ten Theme.

    antworten

Schreibe einen Kommentar


  1. smilkobuta  Januar 25, 2009