Skip to content

Instantly share code, notes, and snippets.

@Wolfsblvt
Last active August 29, 2015 14:14
Show Gist options
  • Save Wolfsblvt/5a496d9955d67507a68e to your computer and use it in GitHub Desktop.
Save Wolfsblvt/5a496d9955d67507a68e to your computer and use it in GitHub Desktop.
Parsing function for mentions
/**
* Replace all mentions inside of a text with full username_string and mention name
*
* @param string $text the original text
* @return string the text with replaced mentions
*/
public function replace_mentions_for_display($text)
{
$start_time = microtime(true);
// Get mentioned users ordered by position descending, so that we can safely replace without conflict
$mentions = $this->mentions->get_mentioned_users($text, true);
foreach ($mentions as $id => $mention_data)
{
$username_full = $mention_data['username_full'];
// If we used a custom text for the mention, we take this as username
if ($mention_data['type'] == mentions::MENTION_BBCODE_TEXT)
{
$username_full = str_replace($mention_data['name'], substr($text, $mention_data['start'], $mention_data['length']), $username_full);
}
// We should cut the bbcodes as well, so we have to look now how long they are
$len_before = $len_after = 0;
switch ($mention_data['type'])
{
case mentions::MENTION_AT:
break;
case mentions::MENTION_BBCODE:
$len_before = strlen('[mention]');
$len_after = strlen('[/mention]');
break;
case mentions::MENTION_BBCODE_TEXT:
$len_before = strlen("[mention="{$mention_data['name']}"]");
$len_after = strlen('[/mention]');
}
// We add the username as title, so that we can see that someone is mentioned and who is mentioned on hover
$a_title = $this->user->lang['MENTIONS_MENTION'] . $this->user->lang['COLON'] . ' ' . $mention_data['name'];
$username_full = preg_replace('#<a#', "<a title=\"$a_title\"", $username_full, 1);
$text = substr_replace($text, $username_full, $mention_data['start'] - $len_before, $mention_data['length'] + $len_before + $len_after);
}
// At last, we should remove the mention tags that still exist.
// We don't go the way of BBCodes, cause we don't have a "real" BBCode with it here and we have
// our own settings wich should matter.
//$text = preg_replace('#\[/?mention(\=&quot;(.*?)&quot;)?\]#i', '', $text);
echo "Runtime of function: " . (microtime(true) - $start_time)/1000 . "<br />";
return $text;
}
/**
* Searches post text for all mentions and returns them as an array
*
* Values of the array are the following:
* 'name' Username
* 'user_id' Users id
* 'posts' Post count
* 'colour' Color of username
* 'avatar' User avatar as full html img element
* 'username_full' Username as html element with color and link
* 'type' Type of the mention
* 'start' Startposition of the username inside the text
* 'end' Endposition of the username inside the text
*
* @param string $post_text The post text
* @param bool $sort_by_position_desc If the returned array should be sorted by position, descending
* @return array Array of all mentioned users and the start position of their name in text
*/
public function get_mentioned_users($post_text, $sort_by_position_desc = false)
{
$mentioned = array();
$user_list = $this->get_userlist();
// At first, the easy part. Let's get the one posted in [mention="{username}"]{text}[/mention]
if (true) // ACP config will come later
{
$regular_expression_match = '#\[mention=&quot;(.+?)&quot;\](.*?)\[/mention\]#';
$matches = false;
preg_match_all($regular_expression_match, $post_text, $matches, PREG_OFFSET_CAPTURE);
for ($i = 0, $len = count($matches[1]); $i < $len; $i++)
{
$username_clean = utf8_clean_string($matches[1][$i][0]);
if (array_key_exists($username_clean, $user_list))
{
$startpos = $matches[2][$i][1];
$length = strlen($matches[2][$i][0]);
$user_data = $user_list[$username_clean];
$mentioned[] = array_merge($user_data, array(
'type' => self::MENTION_BBCODE_TEXT,
'start' => $startpos,
'length' => $length,
));
}
}
}
// Second we get the one posted in [mention]{username}[/mention]
if (true) // ACP config will come later
{
$regular_expression_match = '#\[mention\](.*?)\[/mention\]#';
$matches = false;
preg_match_all($regular_expression_match, $post_text, $matches, PREG_OFFSET_CAPTURE);
for ($i = 0, $len = count($matches[1]); $i < $len; $i++)
{
$username_clean = utf8_clean_string($matches[1][$i][0]);
if (array_key_exists($username_clean, $user_list))
{
$startpos = $matches[1][$i][1];
$length = strlen($matches[1][$i][0]);
$user_data = $user_list[$username_clean];
$mentioned[] = array_merge($user_data, array(
'type' => self::MENTION_BBCODE,
'start' => $startpos,
'length' => $length,
));
}
}
}
// Now the difficult part. Let's see if we can get the correct usernames for the @{username} mentions
if (false) // ACP config will come later
{
$regular_expression_match = '#(?:^|\\s)@(.+?)(?:\n|$)#';
$matches = false;
$offset = 0;
$maximum_at_mentions = $this->config['wolfsblvt.mentions.maximum_at_mentions_per_post'];
$at_mentions = 0;
while ($at_mentions < $maximum_at_mentions && preg_match($regular_expression_match, $post_text, $matches, PREG_OFFSET_CAPTURE, $offset))
{
$at_mentions += 1;
$line = $matches[1][0];
$search_string = substr($line, 0, 1);
$filtered_usernames = array_keys($user_list);
$matched_username = false;
// Loop, make the search string one by one char longer and see if we have still usernames matching
while (count($filtered_usernames) > 1)
{
$filtered_usernames = array_filter($filtered_usernames, function ($username_clean) use ($search_string, &$matched_username) {
$search_string = utf8_clean_string($search_string);
if (strlen($username_clean) == strlen($search_string))
{
if ($username_clean == $search_string)
{
$matched_username = $username_clean;
}
return false;
}
return (substr($username_clean, 0, strlen($search_string)) == $search_string);
});
if ($search_string == $line)
{
// We have reached the end of the line, so stop
break;
}
$search_string = substr($line, 0, strlen($search_string) + 1);
}
// If there is still one in filter, we check if it is matching
$first_username = reset($filtered_usernames);
if (count($filtered_usernames) == 1 && utf8_clean_string(substr($line, 0, strlen($first_username))) == $first_username)
{
$matched_username = $first_username;
}
// We can assume that $matched_username is the longest matching username we have found due to iteration with growing search_string
// So we use it now as the only match (Even if there are maybe shorts usernames matching too. But this is nothing we can solve here,
// This needs to be handled by the user, honestly. There is a autocomplete popup which tells the other, longer fitting name if the user is still typing,
// and if he continues to enter the full name, I think it is okay to choose the longer name as the chosen one.
if ($matched_username)
{
$startpos = $matches[1][1];
// We need to get the endpos, cause the username is cleaned and the real string might be longer
$full_username = substr($post_text, $startpos, strlen($matched_username));
while (utf8_clean_string($full_username) != $matched_username)
{
$full_username = substr($post_text, $startpos, strlen($full_username) + 1);
}
$length = strlen($full_username);
$user_data = $user_list[$matched_username];
$mentioned[] = array_merge($user_data, array(
'type' => self::MENTION_AT,
'start' => $startpos,
'length' => $length,
));
}
$offset = $matches[0][1] + strlen($search_string);
}
}
// Sort the array by descending position so that replacing can be done with correct position values
if ($sort_by_position_desc)
{
usort($mentioned, function ($a, $b) {
return $b['start'] - $a['start'];
});
}
return $mentioned;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment