'
. $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 ''
. '
'
. '
' . $m[2] . '
'
. '
';
},
$s
);
$s = preg_replace_callback(
'/\[spoiler\](.*?)\[\/spoiler\]/is',
function ( $m ) use ( &$spoiler_id ) {
$spoiler_id++;
return ''
. '
'
. '
' . $m[1] . '
'
. '
';
},
$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) . '' . $tag . '>';
},
$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 (, , etc.)
// bereits eigene Zeilenumbrüche erzeugen
return nl2br( $s );
}
}