##// END OF EJS Templates
Make sure users don't get notified for thing they can not view (#3589)....
Jean-Philippe Lang -
r3055:bb477a3a0fe7
parent child
Show More
@@ -1,45 +1,56
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Document < ActiveRecord::Base
19 19 belongs_to :project
20 20 belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id"
21 21 acts_as_attachable :delete_permission => :manage_documents
22 22
23 23 acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
24 24 acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
25 25 :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil },
26 26 :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}}
27 27 acts_as_activity_provider :find_options => {:include => :project}
28 28
29 29 validates_presence_of :project, :title, :category
30 30 validates_length_of :title, :maximum => 60
31 31
32 def visible?(user=User.current)
33 !user.nil? && user.allowed_to?(:view_documents, project)
34 end
35
32 36 def after_initialize
33 37 if new_record?
34 38 self.category ||= DocumentCategory.default
35 39 end
36 40 end
37 41
38 42 def updated_on
39 43 unless @updated_on
40 44 a = attachments.find(:first, :order => 'created_on DESC')
41 45 @updated_on = (a && a.created_on) || created_on
42 46 end
43 47 @updated_on
44 48 end
49
50 # Returns the mail adresses of users that should be notified
51 def recipients
52 notified = project.notified_users
53 notified.reject! {|user| !visible?(user)}
54 notified.collect(&:mail)
55 end
45 56 end
@@ -1,400 +1,403
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Mailer < ActionMailer::Base
19 19 layout 'mailer'
20 20 helper :application
21 21 helper :issues
22 22 helper :custom_fields
23 23
24 24 include ActionController::UrlWriter
25 25 include Redmine::I18n
26 26
27 27 def self.default_url_options
28 28 h = Setting.host_name
29 29 h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
30 30 { :host => h, :protocol => Setting.protocol }
31 31 end
32 32
33 33 # Builds a tmail object used to email recipients of the added issue.
34 34 #
35 35 # Example:
36 36 # issue_add(issue) => tmail object
37 37 # Mailer.deliver_issue_add(issue) => sends an email to issue recipients
38 38 def issue_add(issue)
39 39 redmine_headers 'Project' => issue.project.identifier,
40 40 'Issue-Id' => issue.id,
41 41 'Issue-Author' => issue.author.login
42 42 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
43 43 message_id issue
44 44 recipients issue.recipients
45 45 cc(issue.watcher_recipients - @recipients)
46 46 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
47 47 body :issue => issue,
48 48 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
49 49 render_multipart('issue_add', body)
50 50 end
51 51
52 52 # Builds a tmail object used to email recipients of the edited issue.
53 53 #
54 54 # Example:
55 55 # issue_edit(journal) => tmail object
56 56 # Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
57 57 def issue_edit(journal)
58 58 issue = journal.journalized.reload
59 59 redmine_headers 'Project' => issue.project.identifier,
60 60 'Issue-Id' => issue.id,
61 61 'Issue-Author' => issue.author.login
62 62 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
63 63 message_id journal
64 64 references issue
65 65 @author = journal.user
66 66 recipients issue.recipients
67 67 # Watchers in cc
68 68 cc(issue.watcher_recipients - @recipients)
69 69 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
70 70 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
71 71 s << issue.subject
72 72 subject s
73 73 body :issue => issue,
74 74 :journal => journal,
75 75 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
76 76
77 77 render_multipart('issue_edit', body)
78 78 end
79 79
80 80 def reminder(user, issues, days)
81 81 set_language_if_valid user.language
82 82 recipients user.mail
83 83 subject l(:mail_subject_reminder, issues.size)
84 84 body :issues => issues,
85 85 :days => days,
86 86 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
87 87 render_multipart('reminder', body)
88 88 end
89 89
90 90 # Builds a tmail object used to email users belonging to the added document's project.
91 91 #
92 92 # Example:
93 93 # document_added(document) => tmail object
94 94 # Mailer.deliver_document_added(document) => sends an email to the document's project recipients
95 95 def document_added(document)
96 96 redmine_headers 'Project' => document.project.identifier
97 recipients document.project.recipients
97 recipients document.recipients
98 98 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
99 99 body :document => document,
100 100 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
101 101 render_multipart('document_added', body)
102 102 end
103 103
104 104 # Builds a tmail object used to email recipients of a project when an attachements are added.
105 105 #
106 106 # Example:
107 107 # attachments_added(attachments) => tmail object
108 108 # Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients
109 109 def attachments_added(attachments)
110 110 container = attachments.first.container
111 111 added_to = ''
112 112 added_to_url = ''
113 113 case container.class.name
114 114 when 'Project'
115 115 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
116 116 added_to = "#{l(:label_project)}: #{container}"
117 recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
117 118 when 'Version'
118 119 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
119 120 added_to = "#{l(:label_version)}: #{container.name}"
121 recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
120 122 when 'Document'
121 123 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
122 124 added_to = "#{l(:label_document)}: #{container.title}"
125 recipients container.recipients
123 126 end
124 127 redmine_headers 'Project' => container.project.identifier
125 recipients container.project.recipients
126 128 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
127 129 body :attachments => attachments,
128 130 :added_to => added_to,
129 131 :added_to_url => added_to_url
130 132 render_multipart('attachments_added', body)
131 133 end
132 134
133 135 # Builds a tmail object used to email recipients of a news' project when a news item is added.
134 136 #
135 137 # Example:
136 138 # news_added(news) => tmail object
137 139 # Mailer.deliver_news_added(news) => sends an email to the news' project recipients
138 140 def news_added(news)
139 141 redmine_headers 'Project' => news.project.identifier
140 142 message_id news
141 recipients news.project.recipients
143 recipients news.recipients
142 144 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
143 145 body :news => news,
144 146 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
145 147 render_multipart('news_added', body)
146 148 end
147 149
148 # Builds a tmail object used to email the specified recipients of the specified message that was posted.
150 # Builds a tmail object used to email the recipients of the specified message that was posted.
149 151 #
150 152 # Example:
151 # message_posted(message, recipients) => tmail object
152 # Mailer.deliver_message_posted(message, recipients) => sends an email to the recipients
153 def message_posted(message, recipients)
153 # message_posted(message) => tmail object
154 # Mailer.deliver_message_posted(message) => sends an email to the recipients
155 def message_posted(message)
154 156 redmine_headers 'Project' => message.project.identifier,
155 157 'Topic-Id' => (message.parent_id || message.id)
156 158 message_id message
157 159 references message.parent unless message.parent.nil?
158 recipients(recipients)
160 recipients(message.recipients)
161 cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
159 162 subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
160 163 body :message => message,
161 164 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
162 165 render_multipart('message_posted', body)
163 166 end
164 167
165 168 # Builds a tmail object used to email the recipients of a project of the specified wiki content was added.
166 169 #
167 170 # Example:
168 171 # wiki_content_added(wiki_content) => tmail object
169 172 # Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients
170 173 def wiki_content_added(wiki_content)
171 174 redmine_headers 'Project' => wiki_content.project.identifier,
172 175 'Wiki-Page-Id' => wiki_content.page.id
173 176 message_id wiki_content
174 recipients wiki_content.project.recipients
177 recipients wiki_content.recipients
175 178 cc(wiki_content.page.wiki.watcher_recipients - recipients)
176 179 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :page => wiki_content.page.pretty_title)}"
177 180 body :wiki_content => wiki_content,
178 181 :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title)
179 182 render_multipart('wiki_content_added', body)
180 183 end
181 184
182 185 # Builds a tmail object used to email the recipients of a project of the specified wiki content was updated.
183 186 #
184 187 # Example:
185 188 # wiki_content_updated(wiki_content) => tmail object
186 189 # Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients
187 190 def wiki_content_updated(wiki_content)
188 191 redmine_headers 'Project' => wiki_content.project.identifier,
189 192 'Wiki-Page-Id' => wiki_content.page.id
190 193 message_id wiki_content
191 recipients wiki_content.project.recipients
194 recipients wiki_content.recipients
192 195 cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients)
193 196 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :page => wiki_content.page.pretty_title)}"
194 197 body :wiki_content => wiki_content,
195 198 :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title),
196 199 :wiki_diff_url => url_for(:controller => 'wiki', :action => 'diff', :id => wiki_content.project, :page => wiki_content.page.title, :version => wiki_content.version)
197 200 render_multipart('wiki_content_updated', body)
198 201 end
199 202
200 203 # Builds a tmail object used to email the specified user their account information.
201 204 #
202 205 # Example:
203 206 # account_information(user, password) => tmail object
204 207 # Mailer.deliver_account_information(user, password) => sends account information to the user
205 208 def account_information(user, password)
206 209 set_language_if_valid user.language
207 210 recipients user.mail
208 211 subject l(:mail_subject_register, Setting.app_title)
209 212 body :user => user,
210 213 :password => password,
211 214 :login_url => url_for(:controller => 'account', :action => 'login')
212 215 render_multipart('account_information', body)
213 216 end
214 217
215 218 # Builds a tmail object used to email all active administrators of an account activation request.
216 219 #
217 220 # Example:
218 221 # account_activation_request(user) => tmail object
219 222 # Mailer.deliver_account_activation_request(user)=> sends an email to all active administrators
220 223 def account_activation_request(user)
221 224 # Send the email to all active administrators
222 225 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
223 226 subject l(:mail_subject_account_activation_request, Setting.app_title)
224 227 body :user => user,
225 228 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
226 229 render_multipart('account_activation_request', body)
227 230 end
228 231
229 232 # Builds a tmail object used to email the specified user that their account was activated by an administrator.
230 233 #
231 234 # Example:
232 235 # account_activated(user) => tmail object
233 236 # Mailer.deliver_account_activated(user) => sends an email to the registered user
234 237 def account_activated(user)
235 238 set_language_if_valid user.language
236 239 recipients user.mail
237 240 subject l(:mail_subject_register, Setting.app_title)
238 241 body :user => user,
239 242 :login_url => url_for(:controller => 'account', :action => 'login')
240 243 render_multipart('account_activated', body)
241 244 end
242 245
243 246 def lost_password(token)
244 247 set_language_if_valid(token.user.language)
245 248 recipients token.user.mail
246 249 subject l(:mail_subject_lost_password, Setting.app_title)
247 250 body :token => token,
248 251 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
249 252 render_multipart('lost_password', body)
250 253 end
251 254
252 255 def register(token)
253 256 set_language_if_valid(token.user.language)
254 257 recipients token.user.mail
255 258 subject l(:mail_subject_register, Setting.app_title)
256 259 body :token => token,
257 260 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
258 261 render_multipart('register', body)
259 262 end
260 263
261 264 def test(user)
262 265 set_language_if_valid(user.language)
263 266 recipients user.mail
264 267 subject 'Redmine test'
265 268 body :url => url_for(:controller => 'welcome')
266 269 render_multipart('test', body)
267 270 end
268 271
269 272 # Overrides default deliver! method to prevent from sending an email
270 273 # with no recipient, cc or bcc
271 274 def deliver!(mail = @mail)
272 275 return false if (recipients.nil? || recipients.empty?) &&
273 276 (cc.nil? || cc.empty?) &&
274 277 (bcc.nil? || bcc.empty?)
275 278
276 279 # Set Message-Id and References
277 280 if @message_id_object
278 281 mail.message_id = self.class.message_id_for(@message_id_object)
279 282 end
280 283 if @references_objects
281 284 mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
282 285 end
283 286 super(mail)
284 287 end
285 288
286 289 # Sends reminders to issue assignees
287 290 # Available options:
288 291 # * :days => how many days in the future to remind about (defaults to 7)
289 292 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
290 293 # * :project => id or identifier of project to process (defaults to all projects)
291 294 def self.reminders(options={})
292 295 days = options[:days] || 7
293 296 project = options[:project] ? Project.find(options[:project]) : nil
294 297 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
295 298
296 299 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
297 300 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
298 301 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
299 302 s << "#{Issue.table_name}.project_id = #{project.id}" if project
300 303 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
301 304
302 305 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
303 306 :conditions => s.conditions
304 307 ).group_by(&:assigned_to)
305 308 issues_by_assignee.each do |assignee, issues|
306 309 deliver_reminder(assignee, issues, days) unless assignee.nil?
307 310 end
308 311 end
309 312
310 313 private
311 314 def initialize_defaults(method_name)
312 315 super
313 316 set_language_if_valid Setting.default_language
314 317 from Setting.mail_from
315 318
316 319 # Common headers
317 320 headers 'X-Mailer' => 'Redmine',
318 321 'X-Redmine-Host' => Setting.host_name,
319 322 'X-Redmine-Site' => Setting.app_title,
320 323 'Precedence' => 'bulk',
321 324 'Auto-Submitted' => 'auto-generated'
322 325 end
323 326
324 327 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
325 328 def redmine_headers(h)
326 329 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
327 330 end
328 331
329 332 # Overrides the create_mail method
330 333 def create_mail
331 334 # Removes the current user from the recipients and cc
332 335 # if he doesn't want to receive notifications about what he does
333 336 @author ||= User.current
334 337 if @author.pref[:no_self_notified]
335 338 recipients.delete(@author.mail) if recipients
336 339 cc.delete(@author.mail) if cc
337 340 end
338 341 # Blind carbon copy recipients
339 342 if Setting.bcc_recipients?
340 343 bcc([recipients, cc].flatten.compact.uniq)
341 344 recipients []
342 345 cc []
343 346 end
344 347 super
345 348 end
346 349
347 350 # Rails 2.3 has problems rendering implicit multipart messages with
348 351 # layouts so this method will wrap an multipart messages with
349 352 # explicit parts.
350 353 #
351 354 # https://rails.lighthouseapp.com/projects/8994/tickets/2338-actionmailer-mailer-views-and-content-type
352 355 # https://rails.lighthouseapp.com/projects/8994/tickets/1799-actionmailer-doesnt-set-template_format-when-rendering-layouts
353 356
354 357 def render_multipart(method_name, body)
355 358 if Setting.plain_text_mail?
356 359 content_type "text/plain"
357 360 body render(:file => "#{method_name}.text.plain.rhtml", :body => body, :layout => 'mailer.text.plain.erb')
358 361 else
359 362 content_type "multipart/alternative"
360 363 part :content_type => "text/plain", :body => render(:file => "#{method_name}.text.plain.rhtml", :body => body, :layout => 'mailer.text.plain.erb')
361 364 part :content_type => "text/html", :body => render_message("#{method_name}.text.html.rhtml", body)
362 365 end
363 366 end
364 367
365 368 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
366 369 def self.controller_path
367 370 ''
368 371 end unless respond_to?('controller_path')
369 372
370 373 # Returns a predictable Message-Id for the given object
371 374 def self.message_id_for(object)
372 375 # id + timestamp should reduce the odds of a collision
373 376 # as far as we don't send multiple emails for the same object
374 377 timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
375 378 hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}"
376 379 host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
377 380 host = "#{::Socket.gethostname}.redmine" if host.empty?
378 381 "<#{hash}@#{host}>"
379 382 end
380 383
381 384 private
382 385
383 386 def message_id(object)
384 387 @message_id_object = object
385 388 end
386 389
387 390 def references(object)
388 391 @references_objects ||= []
389 392 @references_objects << object
390 393 end
391 394 end
392 395
393 396 # Patch TMail so that message_id is not overwritten
394 397 module TMail
395 398 class Mail
396 399 def add_message_id( fqdn = nil )
397 400 self.message_id ||= ::TMail::new_message_id(fqdn)
398 401 end
399 402 end
400 403 end
@@ -1,98 +1,105
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Message < ActiveRecord::Base
19 19 belongs_to :board
20 20 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
21 21 acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
22 22 acts_as_attachable
23 23 belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
24 24
25 25 acts_as_searchable :columns => ['subject', 'content'],
26 26 :include => {:board => :project},
27 27 :project_key => 'project_id',
28 28 :date_column => "#{table_name}.created_on"
29 29 acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
30 30 :description => :content,
31 31 :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
32 32 :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} :
33 33 {:id => o.parent_id, :anchor => "message-#{o.id}"})}
34 34
35 35 acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]},
36 36 :author_key => :author_id
37 37 acts_as_watchable
38 38
39 39 attr_protected :locked, :sticky
40 40 validates_presence_of :board, :subject, :content
41 41 validates_length_of :subject, :maximum => 255
42 42
43 43 after_create :add_author_as_watcher
44 44
45 45 def visible?(user=User.current)
46 46 !user.nil? && user.allowed_to?(:view_messages, project)
47 47 end
48 48
49 49 def validate_on_create
50 50 # Can not reply to a locked topic
51 51 errors.add_to_base 'Topic is locked' if root.locked? && self != root
52 52 end
53 53
54 54 def after_create
55 55 if parent
56 56 parent.reload.update_attribute(:last_reply_id, self.id)
57 57 end
58 58 board.reset_counters!
59 59 end
60 60
61 61 def after_update
62 62 if board_id_changed?
63 63 Message.update_all("board_id = #{board_id}", ["id = ? OR parent_id = ?", root.id, root.id])
64 64 Board.reset_counters!(board_id_was)
65 65 Board.reset_counters!(board_id)
66 66 end
67 67 end
68 68
69 69 def after_destroy
70 70 board.reset_counters!
71 71 end
72 72
73 73 def sticky=(arg)
74 74 write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0)
75 75 end
76 76
77 77 def sticky?
78 78 sticky == 1
79 79 end
80 80
81 81 def project
82 82 board.project
83 83 end
84 84
85 85 def editable_by?(usr)
86 86 usr && usr.logged? && (usr.allowed_to?(:edit_messages, project) || (self.author == usr && usr.allowed_to?(:edit_own_messages, project)))
87 87 end
88 88
89 89 def destroyable_by?(usr)
90 90 usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
91 91 end
92 92
93 # Returns the mail adresses of users that should be notified
94 def recipients
95 notified = project.notified_users
96 notified.reject! {|user| !visible?(user)}
97 notified.collect(&:mail)
98 end
99
93 100 private
94 101
95 102 def add_author_as_watcher
96 103 Watcher.create(:watchable => self.root, :user => author)
97 104 end
98 105 end
@@ -1,30 +1,22
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class MessageObserver < ActiveRecord::Observer
19 19 def after_create(message)
20 recipients = []
21 # send notification to the topic watchers
22 recipients += message.root.watcher_recipients
23 # send notification to the board watchers
24 recipients += message.board.watcher_recipients
25 # send notification to project members who want to be notified
26 recipients += message.board.project.recipients
27 recipients = recipients.compact.uniq
28 Mailer.deliver_message_posted(message, recipients) if !recipients.empty? && Setting.notified_events.include?('message_posted')
20 Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
29 21 end
30 22 end
@@ -1,36 +1,47
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class News < ActiveRecord::Base
19 19 belongs_to :project
20 20 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
21 21 has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
22 22
23 23 validates_presence_of :title, :description
24 24 validates_length_of :title, :maximum => 60
25 25 validates_length_of :summary, :maximum => 255
26 26
27 27 acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project
28 28 acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
29 29 acts_as_activity_provider :find_options => {:include => [:project, :author]},
30 30 :author_key => :author_id
31 31
32 def visible?(user=User.current)
33 !user.nil? && user.allowed_to?(:view_news, project)
34 end
35
36 # Returns the mail adresses of users that should be notified
37 def recipients
38 notified = project.notified_users
39 notified.reject! {|user| !visible?(user)}
40 notified.collect(&:mail)
41 end
42
32 43 # returns latest news for projects visible by user
33 44 def self.latest(user = User.current, count = 5)
34 45 find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
35 46 end
36 47 end
@@ -1,95 +1,106
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'zlib'
19 19
20 20 class WikiContent < ActiveRecord::Base
21 21 set_locking_column :version
22 22 belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id'
23 23 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
24 24 validates_presence_of :text
25 25 validates_length_of :comments, :maximum => 255, :allow_nil => true
26 26
27 27 acts_as_versioned
28
29 def visible?(user=User.current)
30 page.visible?(user)
31 end
28 32
29 33 def project
30 34 page.project
31 35 end
32 36
37 # Returns the mail adresses of users that should be notified
38 def recipients
39 notified = project.notified_users
40 notified.reject! {|user| !visible?(user)}
41 notified.collect(&:mail)
42 end
43
33 44 class Version
34 45 belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id'
35 46 belongs_to :author, :class_name => '::User', :foreign_key => 'author_id'
36 47 attr_protected :data
37 48
38 49 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"},
39 50 :description => :comments,
40 51 :datetime => :updated_on,
41 52 :type => 'wiki-page',
42 53 :url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}}
43 54
44 55 acts_as_activity_provider :type => 'wiki_edits',
45 56 :timestamp => "#{WikiContent.versioned_table_name}.updated_on",
46 57 :author_key => "#{WikiContent.versioned_table_name}.author_id",
47 58 :permission => :view_wiki_edits,
48 59 :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
49 60 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
50 61 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
51 62 "#{WikiContent.versioned_table_name}.id",
52 63 :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
53 64 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
54 65 "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
55 66
56 67 def text=(plain)
57 68 case Setting.wiki_compression
58 69 when 'gzip'
59 70 begin
60 71 self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION)
61 72 self.compression = 'gzip'
62 73 rescue
63 74 self.data = plain
64 75 self.compression = ''
65 76 end
66 77 else
67 78 self.data = plain
68 79 self.compression = ''
69 80 end
70 81 plain
71 82 end
72 83
73 84 def text
74 85 @text ||= case compression
75 86 when 'gzip'
76 87 Zlib::Inflate.inflate(data)
77 88 else
78 89 # uncompressed data
79 90 data
80 91 end
81 92 end
82 93
83 94 def project
84 95 page.project
85 96 end
86 97
87 98 # Returns the previous version or nil
88 99 def previous
89 100 @previous ||= WikiContent::Version.find(:first,
90 101 :order => 'version DESC',
91 102 :include => :author,
92 103 :conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version])
93 104 end
94 105 end
95 106 end
@@ -1,259 +1,299
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class MailerTest < ActiveSupport::TestCase
21 21 include Redmine::I18n
22 22 include ActionController::Assertions::SelectorAssertions
23 23 fixtures :projects, :issues, :users, :members, :member_roles, :documents, :attachments, :news, :tokens, :journals, :journal_details, :changesets, :trackers, :issue_statuses, :enumerations, :messages, :boards, :repositories
24 24
25 25 def test_generated_links_in_emails
26 26 ActionMailer::Base.deliveries.clear
27 27 Setting.host_name = 'mydomain.foo'
28 28 Setting.protocol = 'https'
29 29
30 30 journal = Journal.find(2)
31 31 assert Mailer.deliver_issue_edit(journal)
32 32
33 33 mail = ActionMailer::Base.deliveries.last
34 34 assert_kind_of TMail::Mail, mail
35 35
36 36 assert_select_email do
37 37 # link to the main ticket
38 38 assert_select "a[href=?]", "https://mydomain.foo/issues/1", :text => "Bug #1: Can't print recipes"
39 39 # link to a referenced ticket
40 40 assert_select "a[href=?][title=?]", "https://mydomain.foo/issues/2", "Add ingredients categories (Assigned)", :text => "#2"
41 41 # link to a changeset
42 42 assert_select "a[href=?][title=?]", "https://mydomain.foo/projects/ecookbook/repository/revisions/2", "This commit fixes #1, #2 and references #1 &amp; #3", :text => "r2"
43 43 end
44 44 end
45 45
46 46 def test_generated_links_with_prefix
47 47 relative_url_root = Redmine::Utils.relative_url_root
48 48 ActionMailer::Base.deliveries.clear
49 49 Setting.host_name = 'mydomain.foo/rdm'
50 50 Setting.protocol = 'http'
51 51 Redmine::Utils.relative_url_root = '/rdm'
52 52
53 53 journal = Journal.find(2)
54 54 assert Mailer.deliver_issue_edit(journal)
55 55
56 56 mail = ActionMailer::Base.deliveries.last
57 57 assert_kind_of TMail::Mail, mail
58 58
59 59 assert_select_email do
60 60 # link to the main ticket
61 61 assert_select "a[href=?]", "http://mydomain.foo/rdm/issues/1", :text => "Bug #1: Can't print recipes"
62 62 # link to a referenced ticket
63 63 assert_select "a[href=?][title=?]", "http://mydomain.foo/rdm/issues/2", "Add ingredients categories (Assigned)", :text => "#2"
64 64 # link to a changeset
65 65 assert_select "a[href=?][title=?]", "http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2", "This commit fixes #1, #2 and references #1 &amp; #3", :text => "r2"
66 66 end
67 67 ensure
68 68 # restore it
69 69 Redmine::Utils.relative_url_root = relative_url_root
70 70 end
71 71
72 72 def test_generated_links_with_prefix_and_no_relative_url_root
73 73 relative_url_root = Redmine::Utils.relative_url_root
74 74 ActionMailer::Base.deliveries.clear
75 75 Setting.host_name = 'mydomain.foo/rdm'
76 76 Setting.protocol = 'http'
77 77 Redmine::Utils.relative_url_root = nil
78 78
79 79 journal = Journal.find(2)
80 80 assert Mailer.deliver_issue_edit(journal)
81 81
82 82 mail = ActionMailer::Base.deliveries.last
83 83 assert_kind_of TMail::Mail, mail
84 84
85 85 assert_select_email do
86 86 # link to the main ticket
87 87 assert_select "a[href=?]", "http://mydomain.foo/rdm/issues/1", :text => "Bug #1: Can't print recipes"
88 88 # link to a referenced ticket
89 89 assert_select "a[href=?][title=?]", "http://mydomain.foo/rdm/issues/2", "Add ingredients categories (Assigned)", :text => "#2"
90 90 # link to a changeset
91 91 assert_select "a[href=?][title=?]", "http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2", "This commit fixes #1, #2 and references #1 &amp; #3", :text => "r2"
92 92 end
93 93 ensure
94 94 # restore it
95 95 Redmine::Utils.relative_url_root = relative_url_root
96 96 end
97 97
98 98 def test_email_headers
99 99 ActionMailer::Base.deliveries.clear
100 100 issue = Issue.find(1)
101 101 Mailer.deliver_issue_add(issue)
102 102 mail = ActionMailer::Base.deliveries.last
103 103 assert_not_nil mail
104 104 assert_equal 'bulk', mail.header_string('Precedence')
105 105 assert_equal 'auto-generated', mail.header_string('Auto-Submitted')
106 106 end
107 107
108 108 def test_plain_text_mail
109 109 Setting.plain_text_mail = 1
110 110 journal = Journal.find(2)
111 111 Mailer.deliver_issue_edit(journal)
112 112 mail = ActionMailer::Base.deliveries.last
113 113 assert_equal "text/plain", mail.content_type
114 114 assert_equal 0, mail.parts.size
115 115 assert !mail.encoded.include?('href')
116 116 end
117 117
118 118 def test_html_mail
119 119 Setting.plain_text_mail = 0
120 120 journal = Journal.find(2)
121 121 Mailer.deliver_issue_edit(journal)
122 122 mail = ActionMailer::Base.deliveries.last
123 123 assert_equal 2, mail.parts.size
124 124 assert mail.encoded.include?('href')
125 125 end
126 126
127 127 def test_issue_add_message_id
128 128 ActionMailer::Base.deliveries.clear
129 129 issue = Issue.find(1)
130 130 Mailer.deliver_issue_add(issue)
131 131 mail = ActionMailer::Base.deliveries.last
132 132 assert_not_nil mail
133 133 assert_equal Mailer.message_id_for(issue), mail.message_id
134 134 assert_nil mail.references
135 135 end
136 136
137 137 def test_issue_edit_message_id
138 138 ActionMailer::Base.deliveries.clear
139 139 journal = Journal.find(1)
140 140 Mailer.deliver_issue_edit(journal)
141 141 mail = ActionMailer::Base.deliveries.last
142 142 assert_not_nil mail
143 143 assert_equal Mailer.message_id_for(journal), mail.message_id
144 144 assert_equal Mailer.message_id_for(journal.issue), mail.references.first.to_s
145 145 end
146 146
147 147 def test_message_posted_message_id
148 148 ActionMailer::Base.deliveries.clear
149 149 message = Message.find(1)
150 Mailer.deliver_message_posted(message, message.author.mail)
150 Mailer.deliver_message_posted(message)
151 151 mail = ActionMailer::Base.deliveries.last
152 152 assert_not_nil mail
153 153 assert_equal Mailer.message_id_for(message), mail.message_id
154 154 assert_nil mail.references
155 155 end
156 156
157 157 def test_reply_posted_message_id
158 158 ActionMailer::Base.deliveries.clear
159 159 message = Message.find(3)
160 Mailer.deliver_message_posted(message, message.author.mail)
160 Mailer.deliver_message_posted(message)
161 161 mail = ActionMailer::Base.deliveries.last
162 162 assert_not_nil mail
163 163 assert_equal Mailer.message_id_for(message), mail.message_id
164 164 assert_equal Mailer.message_id_for(message.parent), mail.references.first.to_s
165 165 end
166 166
167 context("#issue_add") do
168 setup do
169 ActionMailer::Base.deliveries.clear
170 Setting.bcc_recipients = '1'
171 @issue = Issue.find(1)
172 end
173
174 should "notify project members" do
175 assert Mailer.deliver_issue_add(@issue)
176 assert last_email.bcc.include?('dlopper@somenet.foo')
177 end
178
179 should "not notify project members that are not allow to view the issue" do
180 Role.find(2).remove_permission!(:view_issues)
181 assert Mailer.deliver_issue_add(@issue)
182 assert !last_email.bcc.include?('dlopper@somenet.foo')
183 end
184
185 should "notify issue watchers" do
186 user = User.find(9)
187 Watcher.create!(:watchable => @issue, :user => user)
188 assert Mailer.deliver_issue_add(@issue)
189 assert last_email.bcc.include?(user.mail)
190 end
191
192 should "not notify watchers not allowed to view the issue" do
193 user = User.find(9)
194 Watcher.create!(:watchable => @issue, :user => user)
195 Role.non_member.remove_permission!(:view_issues)
196 assert Mailer.deliver_issue_add(@issue)
197 assert !last_email.bcc.include?(user.mail)
198 end
199 end
200
167 201 # test mailer methods for each language
168 202 def test_issue_add
169 203 issue = Issue.find(1)
170 204 valid_languages.each do |lang|
171 205 Setting.default_language = lang.to_s
172 206 assert Mailer.deliver_issue_add(issue)
173 207 end
174 208 end
175 209
176 210 def test_issue_edit
177 211 journal = Journal.find(1)
178 212 valid_languages.each do |lang|
179 213 Setting.default_language = lang.to_s
180 214 assert Mailer.deliver_issue_edit(journal)
181 215 end
182 216 end
183 217
184 218 def test_document_added
185 219 document = Document.find(1)
186 220 valid_languages.each do |lang|
187 221 Setting.default_language = lang.to_s
188 222 assert Mailer.deliver_document_added(document)
189 223 end
190 224 end
191 225
192 226 def test_attachments_added
193 227 attachements = [ Attachment.find_by_container_type('Document') ]
194 228 valid_languages.each do |lang|
195 229 Setting.default_language = lang.to_s
196 230 assert Mailer.deliver_attachments_added(attachements)
197 231 end
198 232 end
199 233
200 234 def test_news_added
201 235 news = News.find(:first)
202 236 valid_languages.each do |lang|
203 237 Setting.default_language = lang.to_s
204 238 assert Mailer.deliver_news_added(news)
205 239 end
206 240 end
207 241
208 242 def test_message_posted
209 243 message = Message.find(:first)
210 244 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
211 245 recipients = recipients.compact.uniq
212 246 valid_languages.each do |lang|
213 247 Setting.default_language = lang.to_s
214 assert Mailer.deliver_message_posted(message, recipients)
248 assert Mailer.deliver_message_posted(message)
215 249 end
216 250 end
217 251
218 252 def test_account_information
219 253 user = User.find(:first)
220 254 valid_languages.each do |lang|
221 255 user.update_attribute :language, lang.to_s
222 256 user.reload
223 257 assert Mailer.deliver_account_information(user, 'pAsswORd')
224 258 end
225 259 end
226 260
227 261 def test_lost_password
228 262 token = Token.find(2)
229 263 valid_languages.each do |lang|
230 264 token.user.update_attribute :language, lang.to_s
231 265 token.reload
232 266 assert Mailer.deliver_lost_password(token)
233 267 end
234 268 end
235 269
236 270 def test_register
237 271 token = Token.find(1)
238 272 Setting.host_name = 'redmine.foo'
239 273 Setting.protocol = 'https'
240 274
241 275 valid_languages.each do |lang|
242 276 token.user.update_attribute :language, lang.to_s
243 277 token.reload
244 278 ActionMailer::Base.deliveries.clear
245 279 assert Mailer.deliver_register(token)
246 280 mail = ActionMailer::Base.deliveries.last
247 281 assert mail.body.include?("https://redmine.foo/account/activate?token=#{token.value}")
248 282 end
249 283 end
250 284
251 285 def test_reminders
252 286 ActionMailer::Base.deliveries.clear
253 287 Mailer.reminders(:days => 42)
254 288 assert_equal 1, ActionMailer::Base.deliveries.size
255 289 mail = ActionMailer::Base.deliveries.last
256 290 assert mail.bcc.include?('dlopper@somenet.foo')
257 291 assert mail.body.include?('Bug #3: Error 281 when updating a recipe')
258 292 end
293
294 def last_email
295 mail = ActionMailer::Base.deliveries.last
296 assert_not_nil mail
297 mail
298 end
259 299 end
General Comments 0
You need to be logged in to leave comments. Login now