Skip to content

Commit

Permalink
Support @ mentions
Browse files Browse the repository at this point in the history
This provides the basic support for @ mentions.  We can grow this feature
further over time.

Fixes #20837
  • Loading branch information
vboctor committed Apr 28, 2016
1 parent fe73a91 commit 59970d8
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 3 deletions.
6 changes: 6 additions & 0 deletions config_defaults_inc.php
Expand Up @@ -4711,3 +4711,9 @@
*/
$g_webservice_version_when_not_found = '';

/**
* Enables or disables @ mentions feature.
*
* @global integer $g_mentions_enabled
*/
$g_mentions_enabled = ON;
31 changes: 31 additions & 0 deletions core/bug_api.php
Expand Up @@ -41,6 +41,7 @@
* @uses helper_api.php
* @uses history_api.php
* @uses lang_api.php
* @uses mention_api.php
* @uses relationship_api.php
* @uses sponsorship_api.php
* @uses tag_api.php
Expand All @@ -66,6 +67,7 @@
require_api( 'helper_api.php' );
require_api( 'history_api.php' );
require_api( 'lang_api.php' );
require_api( 'mention_api.php' );
require_api( 'relationship_api.php' );
require_api( 'sponsorship_api.php' );
require_api( 'tag_api.php' );
Expand Down Expand Up @@ -477,6 +479,32 @@ function create() {
$this->last_updated = db_now();
}

$t_all_mentioned_user_ids = array();

$t_mentioned_user_ids = mention_get_users( $this->summary );
if( !empty( $t_mentioned_user_ids ) ) {
$this->summary = mention_format_text_save( $this->summary );
$t_all_mentioned_user_ids = array_merge( $t_all_mentioned_user_ids, $t_mentioned_user_ids );
}

$t_mentioned_user_ids = mention_get_users( $this->description );
if( !empty( $t_mentioned_user_ids ) ) {
$this->description = mention_format_text_save( $this->description );
$t_all_mentioned_user_ids = array_merge( $t_all_mentioned_user_ids, $t_mentioned_user_ids );
}

$t_mentioned_user_ids = mention_get_users( $this->steps_to_reproduce );
if( !empty( $t_mentioned_user_ids ) ) {
$this->steps_to_reproduce = mention_format_text_save( $this->steps_to_reproduce );
$t_all_mentioned_user_ids = array_merge( $t_all_mentioned_user_ids, $t_mentioned_user_ids );
}

$t_mentioned_user_ids = mention_get_users( $this->additional_information );
if( !empty( $t_mentioned_user_ids ) ) {
$this->additional_information = mention_format_text_save( $this->additional_information );
$t_all_mentioned_user_ids = array_merge( $t_all_mentioned_user_ids, $t_mentioned_user_ids );
}

# Insert text information
$t_query = 'INSERT INTO {bug_text}
( description, steps_to_reproduce, additional_information )
Expand Down Expand Up @@ -544,6 +572,9 @@ function create() {
history_log_event_direct( $this->id, 'status', $t_original_status, $t_status );
history_log_event_direct( $this->id, 'handler_id', 0, $this->handler_id );

# Now that the issue is added process the @ mentions
mention_process_user_mentions( $this->id, $t_all_mentioned_user_ids );

return $this->id;
}

Expand Down
17 changes: 14 additions & 3 deletions core/bugnote_api.php
Expand Up @@ -37,6 +37,7 @@
* @uses helper_api.php
* @uses history_api.php
* @uses lang_api.php
* @uses mention_api.php
* @uses user_api.php
* @uses utility_api.php
*/
Expand All @@ -55,6 +56,7 @@
require_api( 'helper_api.php' );
require_api( 'history_api.php' );
require_api( 'lang_api.php' );
require_api( 'mention_api.php' );
require_api( 'user_api.php' );
require_api( 'utility_api.php' );

Expand Down Expand Up @@ -192,25 +194,31 @@ function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_

antispam_check();

$t_bugnote_text = $p_bugnote_text;
$t_mentioned_user_ids = mention_get_users( $t_bugnote_text );
if( !empty( $t_mentioned_user_ids ) ) {
$t_bugnote_text = mention_format_text_save( $t_bugnote_text );
}

if( REMINDER !== $p_type ) {
# Check if this is a time-tracking note
$t_time_tracking_enabled = config_get( 'time_tracking_enabled' );
if( ON == $t_time_tracking_enabled && $c_time_tracking > 0 ) {
$t_time_tracking_without_note = config_get( 'time_tracking_without_note' );
if( is_blank( $p_bugnote_text ) && OFF == $t_time_tracking_without_note ) {
if( is_blank( $t_bugnote_text ) && OFF == $t_time_tracking_without_note ) {
error_parameters( lang_get( 'bugnote' ) );
trigger_error( ERROR_EMPTY_FIELD, ERROR );
}
$c_type = TIME_TRACKING;
} else if( is_blank( $p_bugnote_text ) ) {
} else if( is_blank( $t_bugnote_text ) ) {
# This is not time tracking (i.e. it's a normal bugnote)
# @todo should we not trigger an error in this case ?
return false;
}
}

# Event integration
$t_bugnote_text = event_signal( 'EVENT_BUGNOTE_DATA', $p_bugnote_text, $c_bug_id );
$t_bugnote_text = event_signal( 'EVENT_BUGNOTE_DATA', $t_bugnote_text, $c_bug_id );

# insert bugnote text
$t_query = 'INSERT INTO {bugnote_text} ( note ) VALUES ( ' . db_param() . ' )';
Expand Down Expand Up @@ -257,6 +265,9 @@ function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_
history_log_event_special( $p_bug_id, BUGNOTE_ADDED, bugnote_format_id( $t_bugnote_id ) );
}

# Now that the note is added process the @ mentions
mention_process_user_mentions( $p_bug_id, $t_mentioned_user_ids );

# Event integration
event_signal( 'EVENT_BUGNOTE_ADD', array( $p_bug_id, $t_bugnote_id ) );

Expand Down
195 changes: 195 additions & 0 deletions core/mention_api.php
@@ -0,0 +1,195 @@
<?php
# MantisBT - A PHP based bugtracking system

# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT. If not, see <http://www.gnu.org/licenses/>.

/**
* Mention API
*
* @package CoreAPI
* @subpackage LanguageAPI
* @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
* @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net
* @link http://www.mantisbt.org
*
* @uses bug_api.php
* @uses email_api.php
*/

require_api( 'bug_api.php' );
require_api( 'email_api.php' );

/**
* Check if @ mentions feature is enabled or not.
*
* @return bool true enabled, false otherwise.
*/
function mention_enabled() {
return config_get( 'mentions_enabled' ) != OFF;
}

/**
* A method that takes in a text argument and extracts all candidate @ mentions
* from it. The return list will not include the @ sign and will not include
* duplicates. This method is mainly for testability and it doesn't take into
* consideration whether the @ mentions features is enabled or not.
*
* @param string $p_text The text to process.
* @return array of @ mentions without the @ sign.
* @private
*/
function mention_get_candidates( $p_text ) {
preg_match_all( "/(?<!@)@[A-Za-z0-9_\.]+/", $p_text, $t_matches );

$t_mentions = array();

foreach( $t_matches[0] as $t_mention ) {
$t_mention = substr( $t_mention, 1 );

# "victor.boctor" is valid, "vboctor." should be "vboctor"
$t_mention = trim( $t_mention, '.' );
$t_mentions[$t_mention] = true;
}

$t_mentions = array_keys( $t_mentions );

return $t_mentions;
}

/**
* Given a string find the @ mentioned users. The return list is a valid
* list of valid mentioned users. The list will be empty if the mentions
* feature is disabled.
*
* @param string $p_text The text to process.
* @return Array with valid usernames as keys and their ids as values.
*/
function mention_get_users( $p_text ) {
if ( !mention_enabled() ) {
return array();
}

$t_matches = mention_get_candidates( $p_text );
if( empty( $t_matches )) {
return array();
}

$t_mentioned_users = array();

foreach( $t_matches as $t_candidate ) {
if( $t_user_id = user_get_id_by_name( $t_candidate ) ) {
if( false === $t_user_id ) {
continue;
}

$t_mentioned_users[$t_candidate] = $t_user_id;
}
}

return $t_mentioned_users;
}

/**
* Process users that are mentioned on the specified bug.
*
* @param int $p_bug_id The bug id.
* @param array $p_mentioned_user_ids An array of user ids
* @return void
*/
function mention_process_user_mentions( $p_bug_id, $p_mentioned_user_ids ) {
foreach( $p_mentioned_user_ids as $t_mentioned_user_id ) {
bug_monitor( $p_bug_id, $t_mentioned_user_id );
}
}

/**
* Replace the @{U123} with the username or realname based on configuration.
* If user is deleted, use user123.
*
* @param string $p_text The text to process.
* @return string The processed text.
*/
function mention_format_text( $p_text ) {
if ( !mention_enabled() ) {
return $p_text;
}

$t_text = $p_text;

preg_match_all( "/(?<!@)@{U[0-9]+}/", $p_text, $t_matches );

if( !empty( $t_matches[0] ) ) {
$t_matched_mentions = $t_matches[0];
$t_matched_mentions = array_unique( $t_matched_mentions );

$t_formatted_mentions = array();

foreach( $t_matched_mentions as $t_mention ) {
$t_user_id = substr( $t_mention, 3, strlen( $t_mention ) - 4 );
if( $t_username = user_get_name( $t_user_id ) ) {
$t_username = string_display_line( $t_username );

if( user_exists( $t_user_id ) && user_get_field( $t_user_id, 'enabled' ) ) {
$t_user_url = '<a class="user" href="' . string_sanitize_url( 'view_user_page.php?id=' . $t_user_id, true ) . '">@' . $t_username . '</a>';
} else {
$t_user_url = '<del class="user">@' . $t_username . '</del>';
}

$t_formatted_mentions[$t_mention] = "<span class='mention'>{$t_user_url}</span>";
}
}

$t_text = str_replace(
array_keys( $t_formatted_mentions ),
array_values( $t_formatted_mentions ),
$p_text
);
}

return $t_text;
}

/**
* Given a block of text find the @ mentioned users and replace them
* with markup that @ mentions them using their user id. This way the
* mentions don't break if username is changed. Also this allows to show
* username or realname at display time based on configuration.
*
* For example, '@vboctor' will be replaced with '@{U10}' assuming user
* 'vboctor' has id 10.
*
* @param string $p_text The text to process.
* @return string The processed text.
*/
function mention_format_text_save( $p_text ) {
$t_mentioned_users = mention_get_users( $p_text );
if( empty( $t_mentioned_users ) ) {
return $p_text;
}

$t_formatted_mentions = array();

foreach( $t_mentioned_users as $t_user_name => $t_user_id ) {
$t_formatted_mentions[$t_user_name] = "{U" . $t_user_id . "}";
}

$t_text = str_replace(
array_keys( $t_formatted_mentions ),
array_values( $t_formatted_mentions ),
$p_text
);

return $t_text;
}

20 changes: 20 additions & 0 deletions docbook/Admin_Guide/en-US/config/misc.xml
Expand Up @@ -23,6 +23,26 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>$g_mentions_enabled</term>
<listitem>
<para>
Enables or disables the @ mentions feature. Default is ON.
When a user is @ mentioned, they are added to the monitor list
and hence they get a notification about the event (e.g. note addition
or issue submission). Users can be @ mentioned using their username
and not realname.
</para>
<para>
This feature works with fields like summary, description, additional info,
steps to reproduce and notes. Before such fields are saved the @ mention
for username is replaced with a reference to the user id (e.g. @{U123}).
At display time, the reference to the user id is replaced by the username
or real name based on settings. This also allows retaining @ mentions
correctly even if a username is changed or as a user is deleted.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>$g_monitor_bug_threshold</term>
<listitem>
Expand Down
6 changes: 6 additions & 0 deletions plugins/MantisCoreFormatting/MantisCoreFormatting.php
Expand Up @@ -18,6 +18,8 @@
* @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net
*/

require_api( 'mention_api.php' );

/**
* Mantis Core Formatting plugin
*/
Expand Down Expand Up @@ -121,6 +123,10 @@ function formatted( $p_event, $p_string, $p_multiline = true ) {
$t_string = string_process_bug_link( $t_string );
$t_string = string_process_bugnote_link( $t_string );
}

if( mention_enabled() ) {
$t_string = mention_format_text( $t_string );
}

return $t_string;
}
Expand Down
2 changes: 2 additions & 0 deletions tests/Mantis/AllTests.php
Expand Up @@ -30,6 +30,7 @@

require_once 'EnumTest.php';
require_once 'HelperTest.php';
require_once 'MentionTest.php';
require_once 'StringTest.php';

/**
Expand All @@ -48,6 +49,7 @@ public static function suite() {
$t_suite->addTestSuite( 'MantisEnumTest' );
$t_suite->addTestSuite( 'Mantis_HelperTest' );
$t_suite->addTestSuite( 'Mantis_StringTest' );
$t_suite->addTestSuite( 'MentionTest' );

return $t_suite;
}
Expand Down

0 comments on commit 59970d8

Please sign in to comment.