trouble with preg_replace_callback screwing up text

61 views Asked by At

I am trying to replace bbcode quotes like this:

[quote=username]text[/quote] 

which works fine.

If someone is quoting someone, who is also quoting someone else, the problem arises that it only appears to replace one of them, like this sort of text:

[quote=person1][quote=person2][quote]test quoted text[/quote]

another quote[/quote]
one more quote[/quote]

Here are my functions:

// replace specific-user quotes, called by quotes()
function replace_quotes($matches)
{
    global $db;

    $find_quoted = $db->sqlquery("SELECT `username`, `user_id` FROM `users` WHERE `username` = ?", array($matches[1]));
    if ($db->num_rows() == 1)
    {
        $get_quoted = $find_quoted->fetch();
        if (core::config('pretty_urls') == 1)
        {
            $profile_link = '/profiles/' . $get_quoted['user_id'];
        }
        else
        {
            $profile_link = '/index.php?module=profile&user_id=' . $get_quoted['user_id'];
        }
        return '<blockquote><cite><a href="'.$profile_link.'">'.$matches[1].'</a></cite>'.$matches[2].'</blockquote>';
    }
    else
    {
        return '<blockquote><cite>'.$matches[1].'</cite>'.$matches[2].'</blockquote>';
    }
}

// find all quotes
function quotes($body)
{
    // Quoting an actual person, book or whatever
    $pattern = '/\[quote\=(.+?)\](.+?)\[\/quote\]/is';

    $body = preg_replace_callback($pattern, 'replace_quotes', $body);

    // Quote on its own
    $pattern = '/\[quote\](.+?)\[\/quote\]/is';
    $replace = "<blockquote><cite>Quote</cite>$1</blockquote>";

    while(preg_match($pattern, $body))
    {
        $body = preg_replace($pattern, $replace, $body);
    }

    return $body;
}

$body = the actual text sent to it from somewhere, like a comment on something

What am I missing to have the nested stuff work too? As it should be replacing each individual quote.

1

There are 1 answers

1
Casimir et Hippolyte On BEST ANSWER

The idea is to rewrite your function like that (not tested):

function quotes($body)
{
    $pattern = '~\[quote=([^]]+)]([^[]*+(?:\[(?!/?quote\b)[^[]*)*+)\[/quote]~i';
    do {
        $body = preg_replace_callback($pattern, 'replace_quotes', $body, -1, $count);
    } while ($count);

    return $body;
}

Where ([^[]*(?:\[(?!/?quote\b)[^[]*)*) matches only substrings without opening or closing quote tags. This way you are sure to only get the innermost quote tags.

Note that there's an other way in the PHP manual to parse a recursive structure, but I'm not sure it is very efficient. (see the preg_replace_callback page).