' . $m[1] // bereits html-escaped durch htmlspecialchars oben . ''; return $key; }, $out ); // Inline-Code $out = preg_replace_callback( '/\[icode\](.*?)\[\/icode\]/is', function ( $m ) use ( &$placeholders ) { $key = '%%ICODE_' . count($placeholders) . '%%'; $placeholders[$key] = '' . $m[1] . ''; return $key; }, $out ); // 4. Alle anderen Tags parsen $out = self::parse( $out ); // 5. Zeilenumbrüche zu
(nur außerhalb von Block-Tags) $out = self::nl_to_br( $out ); // 6. Code-Blöcke wieder einsetzen foreach ( $placeholders as $key => $html ) { $out = str_replace( $key, $html, $out ); } return $out; } /** * Sanitize bei Speicherung: nur HTML streifen, BBCode-Tags bleiben */ public static function sanitize( $raw ) { // Alle echten HTML-Tags entfernen, BBCode-Tags [xxx] bleiben erhalten return strip_tags( $raw ); } // ── Interner Parser ────────────────────────────────────────────────────── private static function parse( $s ) { // [b] [i] [u] [s] $s = preg_replace( '/\[b\](.*?)\[\/b\]/is', '$1', $s ); $s = preg_replace( '/\[i\](.*?)\[\/i\]/is', '$1', $s ); $s = preg_replace( '/\[u\](.*?)\[\/u\]/is', '$1', $s ); $s = preg_replace( '/\[s\](.*?)\[\/s\]/is', '$1', $s ); // [h2] [h3] $s = preg_replace( '/\[h2\](.*?)\[\/h2\]/is', '

$1

', $s ); $s = preg_replace( '/\[h3\](.*?)\[\/h3\]/is', '

$1

', $s ); // [center] [right] $s = preg_replace( '/\[center\](.*?)\[\/center\]/is', '
$1
', $s ); $s = preg_replace( '/\[right\](.*?)\[\/right\]/is', '
$1
', $s ); // [hr] $s = str_replace( '[hr]', '
', $s ); // [color=...] $s = preg_replace_callback( '/\[color=([a-zA-Z0-9#]{1,20})\](.*?)\[\/color\]/is', function ( $m ) { $color = $m[1]; // Hex-Farben direkt erlauben, benannte aus Whitelist if ( preg_match( '/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $color ) ) { $safe = esc_attr( $color ); } elseif ( in_array( strtolower($color), self::$allowed_colors ) ) { $safe = esc_attr( strtolower($color) ); } else { return $m[2]; // Unbekannte Farbe → nur Text } return '' . $m[2] . ''; }, $s ); // [size=small|large|xlarge] oder [size=1–7] (klassisches BBCode) $s = preg_replace_callback( '/\[size=([a-zA-Z0-9]+)\](.*?)\[\/size\]/is', function ( $m ) { $val = strtolower( $m[1] ); // Benannte Größen $named = [ 'small' => '.8em', 'large' => '1.2em', 'xlarge' => '1.5em' ]; if ( isset( $named[ $val ] ) ) { $size = $named[ $val ]; // Numerische Größen 1–7 (klassisches BBCode-Schema) } elseif ( ctype_digit( $val ) && (int)$val >= 1 && (int)$val <= 7 ) { $num_map = [ 1 => '.7em', 2 => '.85em', 3 => '1em', 4 => '1.2em', 5 => '1.4em', 6 => '1.6em', 7 => '2em' ]; $size = $num_map[ (int)$val ]; } else { return $m[2]; // Unbekannter Wert → nur Text } return '' . $m[2] . ''; }, $s ); // [url=...] und [url]...[/url] $s = preg_replace_callback( '/\[url=([^\]]{1,500})\](.*?)\[\/url\]/is', function ( $m ) { $href = esc_url( $m[1] ); if ( ! $href ) return $m[2]; return '' . $m[2] . ''; }, $s ); $s = preg_replace_callback( '/\[url\](https?:\/\/[^\[]{1,500})\[\/url\]/is', function ( $m ) { $href = esc_url( $m[1] ); if ( ! $href ) return $m[1]; return '' . $href . ''; }, $s ); // [img] $s = preg_replace_callback( '/\[img\](https?:\/\/[^\[]{1,1000})\[\/img\]/is', function ( $m ) { $src = esc_url( $m[1] ); if ( ! $src ) return ''; return ''; }, $s ); // [quote] und [quote=Name] $s = preg_replace_callback( '/\[quote=([^\]]{1,80})\](.*?)\[\/quote\]/is', function ( $m ) { $author = '' . htmlspecialchars( $m[1], ENT_QUOTES ) . ' schrieb:'; return '
' . $author . '' . $m[2] . '
'; }, $s ); $s = preg_replace( '/\[quote\](.*?)\[\/quote\]/is', '
$1
', $s ); // [spoiler] und [spoiler=Titel] static $spoiler_id = 0; $s = preg_replace_callback( '/\[spoiler=([^\]]{0,80})\](.*?)\[\/spoiler\]/is', function ( $m ) use ( &$spoiler_id ) { $spoiler_id++; $title = htmlspecialchars( $m[1] ?: 'Spoiler', ENT_QUOTES ); return '
' . '' . '' . '
'; }, $s ); $s = preg_replace_callback( '/\[spoiler\](.*?)\[\/spoiler\]/is', function ( $m ) use ( &$spoiler_id ) { $spoiler_id++; return '
' . '' . '' . '
'; }, $s ); // [list] [list=1] [*] $s = preg_replace_callback( '/\[list(=1)?\](.*?)\[\/list\]/is', function ( $m ) { $tag = $m[1] ? 'ol' : 'ul'; $items = preg_replace( '/\[\*\]\s*/s', '
  • ', $m[2] ); // Auto-close li tags $items = preg_replace( '/(
  • )(.*?)(?=
  • |$)/s', '$1$2
  • ', $items ); return '<' . $tag . ' class="wbf-bb-list">' . trim($items) . ''; }, $s ); // @Erwähnungen → klickbare Profil-Links $s = preg_replace_callback( '/@([a-zA-Z0-9_]{3,60})\b/', function( $m ) { global $wpdb; $user = $wpdb->get_row( $wpdb->prepare( "SELECT id, username FROM {$wpdb->prefix}forum_users WHERE username=%s", $m[1] ) ); if ( ! $user ) return esc_html($m[0]); return '@' . esc_html($user->username) . ''; }, $s ); return $s; } // Zeilenumbrüche →
    , aber nicht innerhalb von Block-Elementen private static function nl_to_br( $s ) { // Einfaches nl2br — ausreichend da Block-Tags (

    ,