##// END OF EJS Templates
Adds an option to send email on "Assignee updated" in application settings (#16362)....
Jean-Philippe Lang -
r12700:4bf18a697c04
parent child
Show More
@@ -1,199 +1,199
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Journal < ActiveRecord::Base
18 class Journal < ActiveRecord::Base
19 belongs_to :journalized, :polymorphic => true
19 belongs_to :journalized, :polymorphic => true
20 # added as a quick fix to allow eager loading of the polymorphic association
20 # added as a quick fix to allow eager loading of the polymorphic association
21 # since always associated to an issue, for now
21 # since always associated to an issue, for now
22 belongs_to :issue, :foreign_key => :journalized_id
22 belongs_to :issue, :foreign_key => :journalized_id
23
23
24 belongs_to :user
24 belongs_to :user
25 has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
25 has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
26 attr_accessor :indice
26 attr_accessor :indice
27
27
28 acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" },
28 acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" },
29 :description => :notes,
29 :description => :notes,
30 :author => :user,
30 :author => :user,
31 :group => :issue,
31 :group => :issue,
32 :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' },
32 :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' },
33 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
33 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
34
34
35 acts_as_activity_provider :type => 'issues',
35 acts_as_activity_provider :type => 'issues',
36 :author_key => :user_id,
36 :author_key => :user_id,
37 :find_options => {:include => [{:issue => :project}, :details, :user],
37 :find_options => {:include => [{:issue => :project}, :details, :user],
38 :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
38 :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
39 " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
39 " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
40
40
41 before_create :split_private_notes
41 before_create :split_private_notes
42 after_create :send_notification
42 after_create :send_notification
43
43
44 scope :visible, lambda {|*args|
44 scope :visible, lambda {|*args|
45 user = args.shift || User.current
45 user = args.shift || User.current
46
46
47 includes(:issue => :project).
47 includes(:issue => :project).
48 where(Issue.visible_condition(user, *args)).
48 where(Issue.visible_condition(user, *args)).
49 where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false)
49 where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false)
50 }
50 }
51
51
52 def save(*args)
52 def save(*args)
53 # Do not save an empty journal
53 # Do not save an empty journal
54 (details.empty? && notes.blank?) ? false : super
54 (details.empty? && notes.blank?) ? false : super
55 end
55 end
56
56
57 # Returns journal details that are visible to user
57 # Returns journal details that are visible to user
58 def visible_details(user=User.current)
58 def visible_details(user=User.current)
59 details.select do |detail|
59 details.select do |detail|
60 if detail.property == 'cf'
60 if detail.property == 'cf'
61 detail.custom_field && detail.custom_field.visible_by?(project, user)
61 detail.custom_field && detail.custom_field.visible_by?(project, user)
62 elsif detail.property == 'relation'
62 elsif detail.property == 'relation'
63 Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user)
63 Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user)
64 else
64 else
65 true
65 true
66 end
66 end
67 end
67 end
68 end
68 end
69
69
70 def each_notification(users, &block)
70 def each_notification(users, &block)
71 if users.any?
71 if users.any?
72 users_by_details_visibility = users.group_by do |user|
72 users_by_details_visibility = users.group_by do |user|
73 visible_details(user)
73 visible_details(user)
74 end
74 end
75 users_by_details_visibility.each do |visible_details, users|
75 users_by_details_visibility.each do |visible_details, users|
76 if notes? || visible_details.any?
76 if notes? || visible_details.any?
77 yield(users)
77 yield(users)
78 end
78 end
79 end
79 end
80 end
80 end
81 end
81 end
82
82
83 # Returns the JournalDetail for the given attribute, or nil if the attribute
83 # Returns the JournalDetail for the given attribute, or nil if the attribute
84 # was not updated
84 # was not updated
85 def detail_for_attribute(attribute)
85 def detail_for_attribute(attribute)
86 details.detect {|detail| detail.prop_key == attribute}
86 details.detect {|detail| detail.prop_key == attribute}
87 end
87 end
88
88
89 # Returns the new status if the journal contains a status change, otherwise nil
89 # Returns the new status if the journal contains a status change, otherwise nil
90 def new_status
90 def new_status
91 s = new_value_for('status_id')
91 s = new_value_for('status_id')
92 s ? IssueStatus.find_by_id(s.to_i) : nil
92 s ? IssueStatus.find_by_id(s.to_i) : nil
93 end
93 end
94
94
95 def new_value_for(prop)
95 def new_value_for(prop)
96 detail_for_attribute(prop).try(:value)
96 detail_for_attribute(prop).try(:value)
97 end
97 end
98
98
99 def editable_by?(usr)
99 def editable_by?(usr)
100 usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project)))
100 usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project)))
101 end
101 end
102
102
103 def project
103 def project
104 journalized.respond_to?(:project) ? journalized.project : nil
104 journalized.respond_to?(:project) ? journalized.project : nil
105 end
105 end
106
106
107 def attachments
107 def attachments
108 journalized.respond_to?(:attachments) ? journalized.attachments : nil
108 journalized.respond_to?(:attachments) ? journalized.attachments : nil
109 end
109 end
110
110
111 # Returns a string of css classes
111 # Returns a string of css classes
112 def css_classes
112 def css_classes
113 s = 'journal'
113 s = 'journal'
114 s << ' has-notes' unless notes.blank?
114 s << ' has-notes' unless notes.blank?
115 s << ' has-details' unless details.blank?
115 s << ' has-details' unless details.blank?
116 s << ' private-notes' if private_notes?
116 s << ' private-notes' if private_notes?
117 s
117 s
118 end
118 end
119
119
120 def notify?
120 def notify?
121 @notify != false
121 @notify != false
122 end
122 end
123
123
124 def notify=(arg)
124 def notify=(arg)
125 @notify = arg
125 @notify = arg
126 end
126 end
127
127
128 def notified_users
128 def notified_users
129 notified = journalized.notified_users
129 notified = journalized.notified_users
130 if private_notes?
130 if private_notes?
131 notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
131 notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
132 end
132 end
133 notified
133 notified
134 end
134 end
135
135
136 def recipients
136 def recipients
137 notified_users.map(&:mail)
137 notified_users.map(&:mail)
138 end
138 end
139
139
140 def notified_watchers
140 def notified_watchers
141 notified = journalized.notified_watchers
141 notified = journalized.notified_watchers
142 if private_notes?
142 if private_notes?
143 notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
143 notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
144 end
144 end
145 notified
145 notified
146 end
146 end
147
147
148 def watcher_recipients
148 def watcher_recipients
149 notified_watchers.map(&:mail)
149 notified_watchers.map(&:mail)
150 end
150 end
151
151
152 # Sets @custom_field instance variable on journals details using a single query
152 # Sets @custom_field instance variable on journals details using a single query
153 def self.preload_journals_details_custom_fields(journals)
153 def self.preload_journals_details_custom_fields(journals)
154 field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq
154 field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq
155 if field_ids.any?
155 if field_ids.any?
156 fields_by_id = CustomField.where(:id => field_ids).inject({}) {|h, f| h[f.id] = f; h}
156 fields_by_id = CustomField.where(:id => field_ids).inject({}) {|h, f| h[f.id] = f; h}
157 journals.each do |journal|
157 journals.each do |journal|
158 journal.details.each do |detail|
158 journal.details.each do |detail|
159 if detail.property == 'cf'
159 if detail.property == 'cf'
160 detail.instance_variable_set "@custom_field", fields_by_id[detail.prop_key.to_i]
160 detail.instance_variable_set "@custom_field", fields_by_id[detail.prop_key.to_i]
161 end
161 end
162 end
162 end
163 end
163 end
164 end
164 end
165 journals
165 journals
166 end
166 end
167
167
168 private
168 private
169
169
170 def split_private_notes
170 def split_private_notes
171 if private_notes?
171 if private_notes?
172 if notes.present?
172 if notes.present?
173 if details.any?
173 if details.any?
174 # Split the journal (notes/changes) so we don't have half-private journals
174 # Split the journal (notes/changes) so we don't have half-private journals
175 journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false)
175 journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false)
176 journal.details = details
176 journal.details = details
177 journal.save
177 journal.save
178 self.details = []
178 self.details = []
179 self.created_on = journal.created_on
179 self.created_on = journal.created_on
180 end
180 end
181 else
181 else
182 # Blank notes should not be private
182 # Blank notes should not be private
183 self.private_notes = false
183 self.private_notes = false
184 end
184 end
185 end
185 end
186 true
186 true
187 end
187 end
188
188
189 def send_notification
189 def send_notification
190 if notify? && (Setting.notified_events.include?('issue_updated') ||
190 if notify? && (Setting.notified_events.include?('issue_updated') ||
191 (Setting.notified_events.include?('issue_note_added') && notes.present?) ||
191 (Setting.notified_events.include?('issue_note_added') && notes.present?) ||
192 (Setting.notified_events.include?('issue_status_updated') && new_status.present?) ||
192 (Setting.notified_events.include?('issue_status_updated') && new_status.present?) ||
193 (Setting.notified_events.include?('issue_assigned_to_updated') && new_value_for('assigned_to_id').present?) ||
193 (Setting.notified_events.include?('issue_assigned_to_updated') && detail_for_attribute('assigned_to_id').present?) ||
194 (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?)
194 (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?)
195 )
195 )
196 Mailer.deliver_issue_edit(self)
196 Mailer.deliver_issue_edit(self)
197 end
197 end
198 end
198 end
199 end
199 end
General Comments 0
You need to be logged in to leave comments. Login now