diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 8fa56ac55b..0b0dd01e58 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -54,7 +54,8 @@ module ApplicationHelper name = h(user.name(options[:format])) if user.active? || (User.current.admin? && user.logged?) only_path = options[:only_path].nil? ? true : options[:only_path] - link_to name, user_url(user, :only_path => only_path), :class => user.css_classes + css_classes = options[:class] ? "#{user.css_classes} #{options[:class]}" : user.css_classes + link_to name, user_url(user, :only_path => only_path), :class => css_classes else name end @@ -974,9 +975,6 @@ module ApplicationHelper if p = Project.visible.find_by_id(oid) link = link_to_project(p, {:only_path => only_path}, :class => 'project') end - when 'user' - u = User.visible.find_by(:id => oid, :type => 'User') - link = link_to_user(u, :only_path => only_path) if u end elsif sep == ':' name = remove_double_quotes(identifier) @@ -1035,14 +1033,14 @@ module ApplicationHelper if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first link = link_to_project(p, {:only_path => only_path}, :class => 'project') end - when 'user' - u = User.visible.find_by("LOWER(login) = :s AND type = 'User'", :s => name.downcase) - link = link_to_user(u, :only_path => only_path) if u end - elsif sep == "@" - name = remove_double_quotes(identifier) - u = User.visible.find_by("LOWER(login) = :s AND type = 'User'", :s => name.downcase) - link = link_to_user(u, :only_path => only_path) if u + end + if link.nil? && $~ + user = User.mentioned_user($~.named_captures.symbolize_keys) + if user + css_classes = (user.notify_mentioned_user?(obj) ? 'notified' : nil) + link = link_to_user(user, :only_path => only_path, :class => css_classes) + end end end (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) diff --git a/app/models/document.rb b/app/models/document.rb index ccf750fc68..603a535cab 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -61,7 +61,7 @@ class Document < ActiveRecord::Base end def notified_users - project.notified_users.reject {|user| !visible?(user)} + project.notified_users.select {|user| user.allowed_to_view_notify_target?(self) } end private diff --git a/app/models/issue.rb b/app/models/issue.rb index b20da8d91c..435de5912a 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -1038,8 +1038,7 @@ class Issue < ActiveRecord::Base notified += project.notified_users notified.uniq! # Remove users that can not view the issue - notified.reject! {|user| !visible?(user)} - notified + notified.select {|user| user.allowed_to_view_notify_target?(self)} end # Returns the email addresses that should be notified diff --git a/app/models/journal.rb b/app/models/journal.rb index ce3f9d0b32..6bf9690254 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -141,10 +141,7 @@ class Journal < ActiveRecord::Base def notified_users notified = journalized.notified_users - if private_notes? - notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)} - end - notified + notified.select{ |u| u.allowed_to_view_notify_target?(self) } end def recipients diff --git a/app/models/mailer.rb b/app/models/mailer.rb index 92432cf096..350640eb30 100644 --- a/app/models/mailer.rb +++ b/app/models/mailer.rb @@ -90,6 +90,7 @@ class Mailer < ActionMailer::Base # Mailer.deliver_issue_add(issue) def self.deliver_issue_add(issue) users = issue.notified_users | issue.notified_watchers + users |= mentioned_users(issue.description, issue) users.each do |user| issue_add(user, issue).deliver_later end @@ -124,6 +125,7 @@ class Mailer < ActionMailer::Base # Mailer.deliver_issue_edit(journal) def self.deliver_issue_edit(journal) users = journal.notified_users | journal.notified_watchers + users |= mentioned_users(journal.notes, journal) users.select! do |user| journal.notes? || journal.visible_details(user).any? end @@ -149,6 +151,7 @@ class Mailer < ActionMailer::Base # Mailer.deliver_document_added(document, author) def self.deliver_document_added(document, author) users = document.notified_users + users |= mentioned_users(document.description, document) users.each do |user| document_added(user, document, author).deliver_later end @@ -217,6 +220,7 @@ class Mailer < ActionMailer::Base # Mailer.deliver_news_added(news) def self.deliver_news_added(news) users = news.notified_users | news.notified_watchers_for_added_news + users |= mentioned_users(news.description, news) users.each do |user| news_added(user, news).deliver_later end @@ -244,6 +248,7 @@ class Mailer < ActionMailer::Base def self.deliver_news_comment_added(comment) news = comment.commented users = news.notified_users | news.notified_watchers + users |= mentioned_users(comment.content, news) users.each do |user| news_comment_added(user, comment).deliver_later end @@ -271,6 +276,7 @@ class Mailer < ActionMailer::Base users = message.notified_users users |= message.root.notified_watchers users |= message.board.notified_watchers + users |= mentioned_users(message.content, message) users.each do |user| message_posted(user, message).deliver_later @@ -329,6 +335,7 @@ class Mailer < ActionMailer::Base users = wiki_content.notified_users users |= wiki_content.page.notified_watchers users |= wiki_content.page.wiki.notified_watchers + users |= mentioned_users(wiki_content.text, wiki_content) users.each do |user| wiki_content_updated(user, wiki_content).deliver_later @@ -758,5 +765,16 @@ class Mailer < ActionMailer::Base @references_objects ||= [] @references_objects << object end + + def self.mentioned_users(text, obj) + users = [] + return users if text.blank? + text.scan(ApplicationHelper::LINKS_RE) do |_| + target = User.mentioned_user($~.named_captures.symbolize_keys) + next if target.blank? || users.include?(target) + users << target if target.notify_mentioned_user?(obj) + end + users + end end diff --git a/app/models/message.rb b/app/models/message.rb index 83e8cd2c02..cb244defb6 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -103,7 +103,7 @@ class Message < ActiveRecord::Base end def notified_users - project.notified_users.reject {|user| !visible?(user)} + project.notified_users.select {|user| user.allowed_to_view_notify_target?(self) } end private diff --git a/app/models/news.rb b/app/models/news.rb index 90df77e326..d705243d13 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -54,7 +54,7 @@ class News < ActiveRecord::Base end def notified_users - project.users.select {|user| user.notify_about?(self) && user.allowed_to?(:view_news, project)} + project.users.select {|user| user.notify_about?(self) && user.allowed_to_view_notify_target?(self)} end def recipients diff --git a/app/models/user.rb b/app/models/user.rb index 0f82a68068..531f73ab97 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -787,6 +787,17 @@ class User < Principal end end + # Return true if the user is allowed to view the notify target. + def allowed_to_view_notify_target?(object) + case object + when Journal + self.allowed_to_view_notify_target?(object.journalized) && + (!object.private_notes? || self.allowed_to?(:view_private_notes, object.journalized.project)) + else + object.try(:visible?, self) + end + end + def self.current=(user) RequestStore.store[:current_user] = user end @@ -795,6 +806,32 @@ class User < Principal RequestStore.store[:current_user] ||= User.anonymous end + # Return the mentioned user to based on the match data + # of ApplicationHelper::LINKS_RE. + # user:jsmith -> Link to user with login jsmith + # @jsmith -> Link to user with login jsmith + # user#2 -> Link to user with id 2 + def self.mentioned_user(match_data) + return nil if match_data[:esc] + sep = match_data[:sep1] || match_data[:sep2] || match_data[:sep3] || match_data[:sep4] + identifier = match_data[:identifier1] || match_data[:identifier2] || match_data[:identifier3] + prefix = match_data[:prefix] + if (sep == '#' || sep == '##') && prefix == 'user' + User.visible.find_by(:id => identifier.to_i, :type => 'User') + elsif sep == '@' || (sep == ':' && prefix == 'user') + name = identifier.gsub(%r{^"(.*)"$}, "\\1") + User.find_by_login(CGI.unescapeHTML(name).downcase) + end + end + + # Return true if notify the mentioned user. + def notify_mentioned_user?(object) + self.active? && + self.mail.present? && + self.mail_notification.present? && self.mail_notification != 'none' && + self.allowed_to_view_notify_target?(object) + end + # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only # one anonymous user per database. def self.anonymous diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 32e0c84508..85c5535bb8 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -51,7 +51,7 @@ class WikiContent < ActiveRecord::Base end def notified_users - project.notified_users.reject {|user| !visible?(user)} + project.notified_users.select {|user| user.allowed_to_view_notify_target?(self) } end # Returns the mail addresses of users that should be notified diff --git a/test/unit/mailer_test.rb b/test/unit/mailer_test.rb index 2f92d1c603..f1aaca9328 100644 --- a/test/unit/mailer_test.rb +++ b/test/unit/mailer_test.rb @@ -365,6 +365,28 @@ class MailerTest < ActiveSupport::TestCase assert_include 'dlopper@somenet.foo', recipients end + def test_issue_added_should_notify_mentioned_users + # issue with non-public project + issue = Issue.find(4) + # Developer + user = User.find(8) + + # notify mentioned user + ["user##{user.id}", "@#{user.login}", "user:#{user.login}"].each do |description| + issue.update(description: description) + ActionMailer::Base.deliveries.clear + Mailer.deliver_issue_add(issue) + assert_include user.mail, recipients + end + + # Do not notify mentioned users without view_issues permission. + Role.find(2).remove_permission!(:view_issues) + issue.update(description: "user##{user.id}") + ActionMailer::Base.deliveries.clear + Mailer.deliver_issue_add(issue) + assert_not_include user.mail, recipients + end + def test_issue_add_should_send_mail_to_all_user_email_address EmailAddress.create!(:user_id => 3, :address => 'otheremail@somenet.foo') issue = Issue.find(1)