Combine code that finds mentions into one place and add hook points

Combined the code that finds mentions of other profiles into one place.

common_find_mentions() finds mentions and calls hooks to allow
supplemental syntax for mentions (like OStatus).

common_linkify_mentions() links mentions.

common_linkify_mention() links a mention.

Notice::saveReplies() now uses common_find_mentions() instead of
trying to parse everything again.
This commit is contained in:
Evan Prodromou 2010-02-21 16:20:30 -05:00
parent 4209082677
commit ab3db8c899
3 changed files with 184 additions and 119 deletions

View File

@ -748,3 +748,18 @@ EndDisfavorNotice: After saving a notice as a favorite
- $profile: profile of the person faving (can be remote!)
- $notice: notice being faved
StartFindMentions: start finding mentions in a block of text
- $sender: sender profile
- $text: plain text version of the notice
- &$mentions: mentions found so far. Array of arrays; each array
has 'mentioned' (array of mentioned profiles), 'url' (url to link as),
'title' (title of the link), 'position' (position of the text to
replace), 'text' (text to replace)
EndFindMentions: end finding mentions in a block of text
- $sender: sender profile
- $text: plain text version of the notice
- &$mentions: mentions found so far. Array of arrays; each array
has 'mentioned' (array of mentioned profiles), 'url' (url to link as),
'title' (title of the link), 'position' (position of the text to
replace), 'text' (text to replace)

View File

@ -820,6 +820,7 @@ class Notice extends Memcached_DataObject
/**
* @return array of integer profile IDs
*/
function saveReplies()
{
// Don't save reply data for repeats
@ -828,76 +829,44 @@ class Notice extends Memcached_DataObject
return array();
}
// Alternative reply format
$tname = false;
if (preg_match('/^T ([A-Z0-9]{1,64}) /', $this->content, $match)) {
$tname = $match[1];
}
// extract all @messages
$cnt = preg_match_all('/(?:^|\s)@([a-z0-9]{1,64})/', $this->content, $match);
$names = array();
if ($cnt || $tname) {
// XXX: is there another way to make an array copy?
$names = ($tname) ? array_unique(array_merge(array(strtolower($tname)), $match[1])) : array_unique($match[1]);
}
$sender = Profile::staticGet($this->profile_id);
$mentions = common_find_mentions($this->profile_id, $this->content);
$replied = array();
// store replied only for first @ (what user/notice what the reply directed,
// we assume first @ is it)
for ($i=0; $i<count($names); $i++) {
$nickname = $names[$i];
$recipient = common_relative_profile($sender, $nickname, $this->created);
if (empty($recipient)) {
foreach ($mentions as $mention) {
foreach ($mention['mentioned'] as $mentioned) {
// skip if they're already covered
if (!empty($replied[$mentioned->id])) {
continue;
}
// Don't save replies from blocked profile to local user
$recipient_user = User::staticGet('id', $recipient->id);
if (!empty($recipient_user) && $recipient_user->hasBlocked($sender)) {
continue;
}
$reply = new Reply();
$reply->notice_id = $this->id;
$reply->profile_id = $recipient->id;
$id = $reply->insert();
if (!$id) {
$last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
common_log(LOG_ERR, 'DB error inserting reply: ' . $last_error->message);
common_server_error(sprintf(_('DB error inserting reply: %s'), $last_error->message));
return array();
} else {
$replied[$recipient->id] = 1;
}
}
// Hash format replies, too
$cnt = preg_match_all('/(?:^|\s)@#([a-z0-9]{1,64})/', $this->content, $match);
if ($cnt) {
foreach ($match[1] as $tag) {
$tagged = Profile_tag::getTagged($sender->id, $tag);
foreach ($tagged as $t) {
if (!$replied[$t->id]) {
// Don't save replies from blocked profile to local user
$t_user = User::staticGet('id', $t->id);
if ($t_user && $t_user->hasBlocked($sender)) {
$mentioned_user = User::staticGet('id', $mentioned->id);
if (!empty($mentioned_user) && $mentioned_user->hasBlocked($sender)) {
continue;
}
$reply = new Reply();
$reply->notice_id = $this->id;
$reply->profile_id = $t->id;
$reply->profile_id = $mentioned->id;
$id = $reply->insert();
if (!$id) {
common_log_db_error($reply, 'INSERT', __FILE__);
return array();
throw new ServerException("Couldn't save reply for {$this->id}, {$mentioned->id}");
} else {
$replied[$recipient->id] = 1;
}
}
$replied[$mentioned->id] = 1;
}
}
}

View File

@ -426,13 +426,148 @@ function common_render_content($text, $notice)
{
$r = common_render_text($text);
$id = $notice->profile_id;
$r = preg_replace('/(^|\s+)@(['.NICKNAME_FMT.']{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
$r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r);
$r = preg_replace('/(^|[\s\.\,\:\;]+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
$r = common_linkify_mentions($id, $r);
$r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
return $r;
}
function common_linkify_mentions($profile_id, $text)
{
$mentions = common_find_mentions($profile_id, $text);
// We need to go through in reverse order by position,
// so our positions stay valid despite our fudging with the
// string!
$points = array();
foreach ($mentions as $mention)
{
$points[$mention['position']] = $mention;
}
krsort($points);
foreach ($points as $position => $mention) {
$linkText = common_linkify_mention($mention);
$text = substr_replace($text, $linkText, $position, mb_strlen($mention['text']));
}
return $text;
}
function common_linkify_mention($mention)
{
$output = null;
if (Event::handle('StartLinkifyMention', array($mention, &$output))) {
$xs = new XMLStringer(false);
$attrs = array('href' => $mention['url'],
'class' => 'url');
if (!empty($mention['title'])) {
$attrs['title'] = $mention['title'];
}
$xs->elementStart('span', 'vcard');
$xs->elementStart('a', $attrs);
$xs->element('span', 'fn nickname', $mention['text']);
$xs->elementEnd('a');
$xs->elementEnd('span');
$output = $xs->getString();
Event::handle('EndLinkifyMention', array($mention, &$output));
}
return $output;
}
function common_find_mentions($profile_id, $text)
{
$mentions = array();
$sender = Profile::staticGet('id', $profile_id);
if (empty($sender)) {
return $mentions;
}
if (Event::handle('StartFindMentions', array($sender, $text, &$mentions))) {
preg_match_all('/^T ([A-Z0-9]{1,64}) /',
$text,
$tmatches,
PREG_OFFSET_CAPTURE);
preg_match_all('/(?:^|\s+)@(['.NICKNAME_FMT.']{1,64})/',
$text,
$atmatches,
PREG_OFFSET_CAPTURE);
$matches = array_merge($tmatches[1], $atmatches[1]);
foreach ($matches as $match) {
$nickname = common_canonical_nickname($match[0]);
$mentioned = common_relative_profile($sender, $nickname);
if (!empty($mentioned)) {
$user = User::staticGet('id', $mentioned->id);
if ($user) {
$url = common_local_url('userbyid', array('id' => $user->id));
} else {
$url = $mentioned->profileurl;
}
$mention = array('mentioned' => array($mentioned),
'text' => $match[0],
'position' => $match[1],
'url' => $url);
if (!empty($mentioned->fullname)) {
$mention['title'] = $mentioned->fullname;
}
$mentions[] = $mention;
}
}
// @#tag => mention of all subscriptions tagged 'tag'
preg_match_all('/(?:^|[\s\.\,\:\;]+)@#([\pL\pN_\-\.]{1,64})/',
$text,
$hmatches,
PREG_OFFSET_CAPTURE);
foreach ($hmatches[1] as $hmatch) {
$tag = common_canonical_tag($hmatch[0]);
$tagged = Profile_tag::getTagged($sender->id, $tag);
$url = common_local_url('subscriptions',
array('nickname' => $sender->nickname,
'tag' => $tag));
$mentions[] = array('mentioned' => $tagged,
'text' => $hmatch[0],
'position' => $hmatch[1],
'url' => $url);
}
Event::handle('EndFindMentions', array($sender, $text, &$mentions));
}
return $mentions;
}
function common_render_text($text)
{
$r = htmlspecialchars($text);
@ -663,37 +798,6 @@ function common_valid_profile_tag($str)
return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
}
function common_at_link($sender_id, $nickname)
{
$sender = Profile::staticGet($sender_id);
if (!$sender) {
return $nickname;
}
$recipient = common_relative_profile($sender, common_canonical_nickname($nickname));
if ($recipient) {
$user = User::staticGet('id', $recipient->id);
if ($user) {
$url = common_local_url('userbyid', array('id' => $user->id));
} else {
$url = $recipient->profileurl;
}
$xs = new XMLStringer(false);
$attrs = array('href' => $url,
'class' => 'url');
if (!empty($recipient->fullname)) {
$attrs['title'] = $recipient->fullname . ' (' . $recipient->nickname . ')';
}
$xs->elementStart('span', 'vcard');
$xs->elementStart('a', $attrs);
$xs->element('span', 'fn nickname', $nickname);
$xs->elementEnd('a');
$xs->elementEnd('span');
return $xs->getString();
} else {
return $nickname;
}
}
function common_group_link($sender_id, $nickname)
{
$sender = Profile::staticGet($sender_id);
@ -716,29 +820,6 @@ function common_group_link($sender_id, $nickname)
}
}
function common_at_hash_link($sender_id, $tag)
{
$user = User::staticGet($sender_id);
if (!$user) {
return $tag;
}
$tagged = Profile_tag::getTagged($user->id, common_canonical_tag($tag));
if ($tagged) {
$url = common_local_url('subscriptions',
array('nickname' => $user->nickname,
'tag' => $tag));
$xs = new XMLStringer();
$xs->elementStart('span', 'tag');
$xs->element('a', array('href' => $url,
'rel' => $tag),
$tag);
$xs->elementEnd('span');
return $xs->getString();
} else {
return $tag;
}
}
function common_relative_profile($sender, $nickname, $dt=null)
{
// Try to find profiles this profile is subscribed to that have this nickname