summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorMarius Balteanu <marius.balteanu@zitec.com>2022-02-23 21:16:18 +0000
committerMarius Balteanu <marius.balteanu@zitec.com>2022-02-23 21:16:18 +0000
commitba74ba1c702e7a122328094341e659c2baf9fd3d (patch)
treeb6d43d8beaf9538ff54bebd16768f29f7ef782a6 /lib
parent3a6c43deeec8f9f444008e43f53e58a6614716ed (diff)
downloadredmine-ba74ba1c702e7a122328094341e659c2baf9fd3d.tar.gz
redmine-ba74ba1c702e7a122328094341e659c2baf9fd3d.zip
Allow users to be mentioned using @ in issues and wiki pages (#13919):
* the user must have add watchers permission on that object in order to mention other users * mentioned user will receive a notification email * only visible users who can view the object can be mentioned git-svn-id: http://svn.redmine.org/redmine/trunk@21435 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'lib')
-rw-r--r--lib/redmine/acts/mentionable.rb110
-rw-r--r--lib/redmine/preparation.rb8
2 files changed, 115 insertions, 3 deletions
diff --git a/lib/redmine/acts/mentionable.rb b/lib/redmine/acts/mentionable.rb
new file mode 100644
index 000000000..264d1f50b
--- /dev/null
+++ b/lib/redmine/acts/mentionable.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006-2022 Jean-Philippe Lang
+#
+# This program 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.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+module Redmine
+ module Acts
+ module Mentionable
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ def acts_as_mentionable(options = {})
+ class_attribute :mentionable_attributes
+ self.mentionable_attributes = options[:attributes]
+
+ attr_accessor :mentioned_users
+
+ send :include, Redmine::Acts::Mentionable::InstanceMethods
+
+ after_save :parse_mentions
+ end
+ end
+
+ module InstanceMethods
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ def notified_mentions
+ notified = mentioned_users.to_a
+ notified.reject! {|user| user.mail.blank? || user.mail_notification == 'none'}
+ if respond_to?(:visible?)
+ notified.select! {|user| visible?(user)}
+ end
+ notified
+ end
+
+ private
+
+ def parse_mentions
+ mentionable_attrs = self.mentionable_attributes
+ saved_mentionable_attrs = self.saved_changes.select{|a| mentionable_attrs.include?(a)}
+
+ saved_mentionable_attrs.each do |key, attr|
+ old_value, new_value = attr
+ get_mentioned_users(old_value, new_value)
+ end
+ end
+
+ def get_mentioned_users(old_content, new_content)
+ self.mentioned_users = []
+
+ previous_matches = scan_for_mentioned_users(old_content)
+ current_matches = scan_for_mentioned_users(new_content)
+ new_matches = (current_matches - previous_matches).flatten
+
+ if new_matches.any?
+ self.mentioned_users = User.visible.active.where(login: new_matches)
+ end
+ end
+
+ def scan_for_mentioned_users(content)
+ return [] if content.nil?
+
+ # remove quoted text
+ content = content.gsub(%r{\r\n(?:\>\s)+(.*?)\r\n}m, '')
+
+ text_formatting = Setting.text_formatting
+ # Remove text wrapped in pre tags based on text formatting
+ case text_formatting
+ when 'textile'
+ content = content.gsub(%r{<pre>(.*?)</pre>}m, '')
+ when 'markdown', 'common_mark'
+ content = content.gsub(%r{(~~~|```)(.*?)(~~~|```)}m, '')
+ end
+
+ users = content.scan(MENTION_PATTERN).flatten
+ end
+
+ MENTION_PATTERN = /
+ (?:^|\W) # beginning of string or non-word char
+ @((?>[a-z0-9][a-z0-9-]*)) # @username
+ (?!\/) # without a trailing slash
+ (?=
+ \.+[ \t\W]| # dots followed by space or non-word character
+ \.+$| # dots at end of line
+ [^0-9a-zA-Z_.]| # non-word character except dot
+ $ # end of line
+ )
+ /ix
+ end
+ end
+ end
+end
diff --git a/lib/redmine/preparation.rb b/lib/redmine/preparation.rb
index 4c6b3afe6..2ba3b5447 100644
--- a/lib/redmine/preparation.rb
+++ b/lib/redmine/preparation.rb
@@ -21,6 +21,7 @@ module Redmine
module Preparation
def self.prepare
ActiveRecord::Base.include Redmine::Acts::Positioned
+ ActiveRecord::Base.include Redmine::Acts::Mentionable
ActiveRecord::Base.include Redmine::I18n
Scm::Base.add "Subversion"
@@ -71,9 +72,10 @@ module Redmine
map.permission :view_private_notes, {}, :read => true, :require => :member
map.permission :set_notes_private, {}, :require => :member
map.permission :delete_issues, {:issues => :destroy}, :require => :member
+ map.permission :mention_users, {}
# Watchers
map.permission :view_issue_watchers, {}, :read => true
- map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
+ map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user, :autocomplete_for_mention]}
map.permission :delete_issue_watchers, {:watchers => :destroy}
map.permission :import_issues, {}
# Issue categories
@@ -123,7 +125,7 @@ module Redmine
map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member
map.permission :delete_wiki_pages_attachments, {}
map.permission :view_wiki_page_watchers, {}, :read => true
- map.permission :add_wiki_page_watchers, {:watchers => [:new, :create, :autocomplete_for_user]}
+ map.permission :add_wiki_page_watchers, {:watchers => [:new, :create, :autocomplete_for_user, :autocomplete_for_mention]}
map.permission :delete_wiki_page_watchers, {:watchers => :destroy}
map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
map.permission :manage_wiki, {:wikis => :destroy, :wiki => :rename}, :require => :member
@@ -145,7 +147,7 @@ module Redmine
map.permission :delete_messages, {:messages => :destroy}, :require => :member
map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
map.permission :view_message_watchers, {}, :read => true
- map.permission :add_message_watchers, {:watchers => [:new, :create, :autocomplete_for_user]}
+ map.permission :add_message_watchers, {:watchers => [:new, :create, :autocomplete_for_user, :autocomplete_for_mention]}
map.permission :delete_message_watchers, {:watchers => :destroy}
map.permission :manage_boards, {:projects => :settings, :boards => [:new, :create, :edit, :update, :destroy]}, :require => :member
end