Probleme mit ShortCodes

Seid Wor­d­Press 2.5 bie­tet das Blogging-System sei­nen Erwei­te­run­gen (Plugins) die neue Mög­lich­keit an,  mit so genann­ten Short­Codes Inhal­te ein­fach und dyna­misch zu modi­fi­zie­ren. Dazu muss eine Modifikations-Funktion auf einen ent­spre­chen­den Short­Code regis­triert wer­den, wel­cher dann auto­ma­tisch auf­ge­ru­fen wird. Ein Short­Code kann dabei noch zusätz­li­che Para­me­ter defi­nie­ren, die dann die­ser Funk­ti­on zur Aus­wer­tung durch­ge­reicht wer­den (Bei­spiel mit zwei Para­me­ter: [ShortCode Param1=Wert1 Param2=Wert2]). Eines der berühm­tes­ten WordPress-Erweiterungen, wel­che in der neus­ten Ver­si­on die­se Tech­no­lo­gie ein­setzt, ist die Gallery-Verwaltung Next­GEN Gal­le­ry. Und mit ihrem Ein­satz fällt ein klei­ner Feh­ler in Wor­d­Press auf: Short­Codes kom­bi­niert mit sehr lan­gen Bei­trä­gen füh­ren bei der Anzei­ge ger­ne zu einem lee­ren Ergebnis.

Dies sieht sehr ver­däch­tig nach einem Speicher-Problem aus. Doch wo genau tritt der Feh­ler auf und was kann man dage­gen unter­neh­men? Ein klei­ner Such­trip durch den Quell­code von Wor­d­Press (zum Zeit­punkt die­ses Arti­kels in der Ver­si­on 2.7 ) zeig­te bald, dass der Inhalt einer mei­ner lan­gen Arti­kel in der Funk­ti­on wpautop() in der Datei ./wp-includes/formatting.php ver­lo­ren ging. Und zwar genau in der vor­letz­te Zei­le der Funktion:

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

(Wor­d­Press 2.7, Datei formatting.php, Funk­ti­on wpautop(), Zei­le 153)
Die­se Funk­ti­on sorgt dafür, dass vor der Aus­ga­be der Bei­trä­ge Zei­len­um­brü­che in HTML-Zeilenumbrüche (<br />) und HTML-Paragraphen (<p />) umge­wan­delt wer­den. Sie wird vor der Erset­zung der Short­Codes auf­ge­ru­fen. Die letz­te Erset­zung sucht nun allein ste­hen­de Short­Codes und ent­fernt mög­li­che (auto­ma­tisch hin­zu­ge­füg­te) Para­gra­phen um die­se. Die hier auf­ge­ru­fe­ne Metho­de get_shortcode_regex() lie­fert dabei das Such­mus­ter für die defi­nier­ten Short­Codes zurück. Die­se befin­det sich in der Datei ./wp-includes/shortcodes.php ab der Zei­le 165. Der hier zurück gelie­fer­te regu­lä­re Aus­druck sucht nach einem Mus­ter in der fol­gen­den Form:

ShortCodes: Problematischer RegEx

Short­Codes: Pro­ble­ma­ti­scher RegEx

In die­sem Aus­druck wer­den zwei nicht-gierige (non-greedy, lazy) Quan­to­ren ein­ge­setzt. Dies bedeu­tet, es wird sehr viel Back­tracking betrie­ben: die RegEx-Engine über­springt im ers­ten Schritt die Zei­chen, wel­che auf sol­che nicht-gierigen Quan­to­ren pas­sen, da ja an die­ser Stel­le ver­sucht wird, so wenig wie mög­lich ein­zu­fan­gen. Trotz­dem merkt sich die Engi­ne, wo sie not­falls nach­schau­en muss, falls der gan­ze rest­li­che Text so erst­mal nicht in das Such­mus­ter passt. Der “gan­ze Rest” bei gro­ßen Bei­trä­gen mit meh­re­ren nicht-gierigen Quan­to­ren kann ganz schön viel sein, was die His­to­rie ger­ne anwach­sen lässt. Wir dür­fen nicht ver­ges­sen: die RegEx-Engine geht am Ende nach und nach wie­der ein Schritt zurück und prüft erneut nach, ob sie ein Ergeb­nis fin­det (Back­tracking). Um den Auf­wand zu mini­mie­ren, bie­tet es sich an, den zu über­prü­fen­den “Rest” lokal ein­zu­schrän­ken. In unse­rem Fall wird der Zei­chen­be­reich der Para­me­ter­paa­re mit dem End­zei­chen ”]” eines Tags begrenzt. Ändern wir nun den roten Abschnitt (.*?) zu ([^]]*?), tren­nen wir somit die Non-Greedy-Bereiche (Para­me­ter­paa­re und mög­li­che Zwi­schen­in­hal­te) und mini­mie­ren den Auf­wand und somit den Spei­cher­ver­brauch. Unser über­lan­ger Arti­kel müss­te wie­der erscheinen.
Mit einer klei­ne Schön­heits­ope­ra­ti­on kön­nen wir noch die (in mei­nen Augen) unnüt­ze grü­ne, nicht-einfangende Klam­me­rung um den ein­ge­fan­ge­nen optio­na­le Schräg­strich ent­fer­nen: (?:(/))? zu (/)?. Die modi­fi­zier­te Metho­de 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])?';
}

(Wor­d­Press 2.7, Funk­ti­on get_shortcode_regex(), Zei­le 165 in Datei shortcodes.php)
(Pro­blems in Wor­d­Press with long posts and plugins like Next­GEN Gallery)

7

Kommentare

  1. smilkobuta  Januar 25, 2009

    Very Thanks to you!!!
    I final­ly unders­tood the source of this pro­blem and pro­blem itself.
    I can’t read Ger­man but your com­ment image is very easy to understand.
    Dan­ke schön!!

    antworten
  2. smilkobuta  Januar 25, 2009

    I report your code to Wor­d­Press Trac
    http://trac.wordpress.org/ticket/8962

    antworten
  3. Matthias Brusdeylins  Januar 27, 2009

    Thanks for repor­ting this bug to WP Trac. I hope they under­stand, that they have to divi­de the two Non-Greedy-Char-Areas (in our examp­le on the first squa­re bra­cket ”]” — red text). So the engi­ne needs less back­trackings and in this case less memory…
    that’s all… 🙂

    antworten
  4. Matthias Brusdeylins  Juni 16, 2009

    Auch in Wor­d­Press 2.8 ist der Feh­ler wei­ter­hin vorhanden.
    Hier liegt der Speicher-Fresser eben­falls in der nicht-gierigen Klam­me­rung. Die letz­te Zei­le in der Funk­ti­on get_shortcode_regex() lau­tet hier nun korrigiert:
    return '(.?)[('.$tagregexp.')b([^]]*?)(/)?](?:(.+?)[/2])?(.?)';
    Das Ticket ist wei­ter­hin offen:
    http://core.trac.wordpress.org/ticket/8553
    WP 2.9 soll es rich­ten… dabei ist es sooo einfach…

    antworten
  5. Matthias Brusdeylins  November 21, 2009

    In Wor­d­Press 2.8.6 besteht das Pro­blem wei­ter­hin. Der Fix oben (Aus­tausch der RegEx-Zeile) geht aber auch hier und muss nach dem Update wie­der durch­ge­führt werden…

    antworten
  6. Matthias Brusdeylins  Dezember 23, 2009

    Mit Wor­d­Press 2.9 ist die­ses Pro­blem noch grö­ßer gewor­den. Der hier beschrei­be­ne Fix reicht wohl nicht mehr aus. Da unter WP 2.9 auch mein The­me nicht mehr funk­tio­niert (war­um auch immer ?!), wer­de ich mich die­sem The­ma wohl erst ab WP 3.0 anneh­men. Dann mit einem kom­plett über­ar­bei­te­ten Theme.

    antworten

Schreibe einen Kommentar


Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

  1. smilkobuta  Januar 25, 2009