开发者

How to wrap user mentions in a HTML link on PHP?

Im working on a commenting web application and i want to parse user mentions (@user) as links. Here is what I have so far:

$text = "@user is not @user1 but @user3 is @user4";

$pattern = "/\@(\w+)/";
preg_match_all($pattern,$text,$matches);

if($matches){   

    $sql = "SELECT * 
            FROM users 
            WHERE username IN ('" .implode("','",$matches[1]). "')
            ORDER BY LENGTH(username) DESC";
    $users = $this->getQuery($sql);

    foreach($users as $i=>$u){
        $text = str_replace("@{$u['username']}",
        "<a href='#' class='ct-userLink' rel='{$u['user_id']}'>@{$u['username']}</a> ", $text);
    }

    开发者_开发问答$echo $text;
}

The problem is that user links are being overlapped:

<a rel="11327" class="ct-userLink" href="#">
    <a rel="21327" class="ct-userLink" href="#">@user</a>1
</a>

How can I avoid links overlapping?

Answer Update

Thanks to the answer picked, this is how my new foreach loop looks like:

foreach($users as $i=>$u){
    $text = preg_replace("/@".$u['username']."\b/",
    "<a href='#' title='{$u['user_id']}'>@{$u['username']}</a> ", $text);
}


Problem seems to be that some usernames can encompass other usernames. So you replace user1 properly with <a>user1</a>. Then, user matches and replaces with <a><a>user</a>1</a>. My suggestion is to change your string replace to a regex with a word boundary, \b, that is required after the username.


The Twitter widget has JavaScript code to do this. I ported it to PHP in my WordPress plugin. Here's the relevant part:

function format_tweet($tweet) {
    // add @reply links
    $tweet_text = preg_replace("/\B[@@]([a-zA-Z0-9_]{1,20})/",
              "@<a class='atreply' href='http://twitter.com/$1'>$1</a>",
              $tweet);

    // make other links clickable
    $matches = array();
    $link_info = preg_match_all("/\b(((https*\:\/\/)|www\.)[^\"\']+?)(([!?,.\)]+)?(\s|$))/",
                 $tweet_text, $matches, PREG_SET_ORDER);

    if ($link_info) {
      foreach ($matches as $match) {
        $http = preg_match("/w/", $match[2]) ? 'http://' : '';
        $tweet_text = str_replace($match[0],
            "<a href='" . $http . $match[1] . "'>" . $match[1] . "</a>" . $match[4],
        $tweet_text);
      }
    }
    return $tweet_text;
}


instead of parsing for '@user' parse for '@user ' (with space in the end) or ' @user ' to even avoid wrong parsing of email addresses (eg: mailaddress@user.com) maybe ' @user: ' should also be allowed. this will only work, if usernames have no whitespaces...


You can go for a custom str replace function which stops at first replace.. Something like ...

function str_replace_once($needle , $replace , $haystack){       
        $pos = strpos($haystack, $needle);
    if ($pos === false) {
        // Nothing found
    return $haystack;
    }
    return substr_replace($haystack, $replace, $pos, strlen($needle));
}  

And use it like:

 foreach($users as $i=>$u){
        $text = str_replace_once("@{$u['username']}",
        "<a href='#' class='ct-userLink' rel='{$u['user_id']}'>@{$u['username']}</a> ", $text);
    }


You shouldn’t replace one certain user mention at a time but all at once. You could use preg_split to do that:

// split text at mention while retaining user name
$parts = preg_split("/@(\w+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$n = count($parts);

// $n is always an odd number; 1 means no match found
if ($n > 1) {
    // collect user names
    $users = array();
    for ($i=1; $i<$n; $i+=2) {
        $users[$parts[$i]] = '';
    }

    // get corresponding user information
    $sql = "SELECT * 
            FROM users 
            WHERE username IN ('" .implode("','", array_keys($users)). "')";
    $users = array();
    foreach ($this->getQuery($sql) as $user) {
        $users[$user['username']] = $user;
    }

    // replace mentions
    for ($i=1; $i<$n; $i+=2) {
        $u = $users[$parts[$i]];
        $parts[$i] = "<a href='#' class='ct-userLink' rel='{$u['user_id']}'>@{$u['username']}</a>";
    }

    // put everything back together
    $text = implode('', $parts);
}


I like dnl solution of parsing ' @user', but maybe is not suitable for you.

Anyway, did you try to use strip_tags function to remove the anchor tags? That way you have the string without the links, and you can parse it building the links again.

strip_tags

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜