annotate app/models/mailer.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children 1d32c0a0efbf
rev   line source
Chris@0 1 # redMine - project management software
Chris@0 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 class Mailer < ActionMailer::Base
Chris@0 19 layout 'mailer'
Chris@0 20 helper :application
Chris@0 21 helper :issues
Chris@0 22 helper :custom_fields
Chris@0 23
Chris@0 24 include ActionController::UrlWriter
Chris@0 25 include Redmine::I18n
Chris@0 26
Chris@0 27 def self.default_url_options
Chris@0 28 h = Setting.host_name
Chris@0 29 h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
Chris@0 30 { :host => h, :protocol => Setting.protocol }
Chris@0 31 end
Chris@0 32
Chris@0 33 # Builds a tmail object used to email recipients of the added issue.
Chris@0 34 #
Chris@0 35 # Example:
Chris@0 36 # issue_add(issue) => tmail object
Chris@0 37 # Mailer.deliver_issue_add(issue) => sends an email to issue recipients
Chris@0 38 def issue_add(issue)
Chris@0 39 redmine_headers 'Project' => issue.project.identifier,
Chris@0 40 'Issue-Id' => issue.id,
Chris@0 41 'Issue-Author' => issue.author.login
Chris@0 42 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
Chris@0 43 message_id issue
Chris@0 44 recipients issue.recipients
Chris@0 45 cc(issue.watcher_recipients - @recipients)
Chris@0 46 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
Chris@0 47 body :issue => issue,
Chris@0 48 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
Chris@0 49 render_multipart('issue_add', body)
Chris@0 50 end
Chris@0 51
Chris@0 52 # Builds a tmail object used to email recipients of the edited issue.
Chris@0 53 #
Chris@0 54 # Example:
Chris@0 55 # issue_edit(journal) => tmail object
Chris@0 56 # Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
Chris@0 57 def issue_edit(journal)
Chris@0 58 issue = journal.journalized.reload
Chris@0 59 redmine_headers 'Project' => issue.project.identifier,
Chris@0 60 'Issue-Id' => issue.id,
Chris@0 61 'Issue-Author' => issue.author.login
Chris@0 62 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
Chris@0 63 message_id journal
Chris@0 64 references issue
Chris@0 65 @author = journal.user
Chris@0 66 recipients issue.recipients
Chris@0 67 # Watchers in cc
Chris@0 68 cc(issue.watcher_recipients - @recipients)
Chris@0 69 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
Chris@0 70 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
Chris@0 71 s << issue.subject
Chris@0 72 subject s
Chris@0 73 body :issue => issue,
Chris@0 74 :journal => journal,
Chris@0 75 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
Chris@0 76
Chris@0 77 render_multipart('issue_edit', body)
Chris@0 78 end
Chris@0 79
Chris@0 80 def reminder(user, issues, days)
Chris@0 81 set_language_if_valid user.language
Chris@0 82 recipients user.mail
Chris@0 83 subject l(:mail_subject_reminder, issues.size)
Chris@0 84 body :issues => issues,
Chris@0 85 :days => days,
Chris@0 86 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
Chris@0 87 render_multipart('reminder', body)
Chris@0 88 end
Chris@0 89
Chris@0 90 # Builds a tmail object used to email users belonging to the added document's project.
Chris@0 91 #
Chris@0 92 # Example:
Chris@0 93 # document_added(document) => tmail object
Chris@0 94 # Mailer.deliver_document_added(document) => sends an email to the document's project recipients
Chris@0 95 def document_added(document)
Chris@0 96 redmine_headers 'Project' => document.project.identifier
Chris@0 97 recipients document.recipients
Chris@0 98 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
Chris@0 99 body :document => document,
Chris@0 100 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
Chris@0 101 render_multipart('document_added', body)
Chris@0 102 end
Chris@0 103
Chris@0 104 # Builds a tmail object used to email recipients of a project when an attachements are added.
Chris@0 105 #
Chris@0 106 # Example:
Chris@0 107 # attachments_added(attachments) => tmail object
Chris@0 108 # Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients
Chris@0 109 def attachments_added(attachments)
Chris@0 110 container = attachments.first.container
Chris@0 111 added_to = ''
Chris@0 112 added_to_url = ''
Chris@0 113 case container.class.name
Chris@0 114 when 'Project'
Chris@0 115 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
Chris@0 116 added_to = "#{l(:label_project)}: #{container}"
Chris@0 117 recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
Chris@0 118 when 'Version'
Chris@0 119 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
Chris@0 120 added_to = "#{l(:label_version)}: #{container.name}"
Chris@0 121 recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
Chris@0 122 when 'Document'
Chris@0 123 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
Chris@0 124 added_to = "#{l(:label_document)}: #{container.title}"
Chris@0 125 recipients container.recipients
Chris@0 126 end
Chris@0 127 redmine_headers 'Project' => container.project.identifier
Chris@0 128 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
Chris@0 129 body :attachments => attachments,
Chris@0 130 :added_to => added_to,
Chris@0 131 :added_to_url => added_to_url
Chris@0 132 render_multipart('attachments_added', body)
Chris@0 133 end
Chris@0 134
Chris@0 135 # Builds a tmail object used to email recipients of a news' project when a news item is added.
Chris@0 136 #
Chris@0 137 # Example:
Chris@0 138 # news_added(news) => tmail object
Chris@0 139 # Mailer.deliver_news_added(news) => sends an email to the news' project recipients
Chris@0 140 def news_added(news)
Chris@0 141 redmine_headers 'Project' => news.project.identifier
Chris@0 142 message_id news
Chris@0 143 recipients news.recipients
Chris@0 144 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
Chris@0 145 body :news => news,
Chris@0 146 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
Chris@0 147 render_multipart('news_added', body)
Chris@0 148 end
Chris@0 149
Chris@0 150 # Builds a tmail object used to email the recipients of the specified message that was posted.
Chris@0 151 #
Chris@0 152 # Example:
Chris@0 153 # message_posted(message) => tmail object
Chris@0 154 # Mailer.deliver_message_posted(message) => sends an email to the recipients
Chris@0 155 def message_posted(message)
Chris@0 156 redmine_headers 'Project' => message.project.identifier,
Chris@0 157 'Topic-Id' => (message.parent_id || message.id)
Chris@0 158 message_id message
Chris@0 159 references message.parent unless message.parent.nil?
Chris@0 160 recipients(message.recipients)
Chris@0 161 cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
Chris@0 162 subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
Chris@0 163 body :message => message,
Chris@0 164 :message_url => url_for(message.event_url)
Chris@0 165 render_multipart('message_posted', body)
Chris@0 166 end
Chris@0 167
Chris@0 168 # Builds a tmail object used to email the recipients of a project of the specified wiki content was added.
Chris@0 169 #
Chris@0 170 # Example:
Chris@0 171 # wiki_content_added(wiki_content) => tmail object
Chris@0 172 # Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients
Chris@0 173 def wiki_content_added(wiki_content)
Chris@0 174 redmine_headers 'Project' => wiki_content.project.identifier,
Chris@0 175 'Wiki-Page-Id' => wiki_content.page.id
Chris@0 176 message_id wiki_content
Chris@0 177 recipients wiki_content.recipients
Chris@0 178 cc(wiki_content.page.wiki.watcher_recipients - recipients)
Chris@0 179 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :page => wiki_content.page.pretty_title)}"
Chris@0 180 body :wiki_content => wiki_content,
Chris@0 181 :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title)
Chris@0 182 render_multipart('wiki_content_added', body)
Chris@0 183 end
Chris@0 184
Chris@0 185 # Builds a tmail object used to email the recipients of a project of the specified wiki content was updated.
Chris@0 186 #
Chris@0 187 # Example:
Chris@0 188 # wiki_content_updated(wiki_content) => tmail object
Chris@0 189 # Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients
Chris@0 190 def wiki_content_updated(wiki_content)
Chris@0 191 redmine_headers 'Project' => wiki_content.project.identifier,
Chris@0 192 'Wiki-Page-Id' => wiki_content.page.id
Chris@0 193 message_id wiki_content
Chris@0 194 recipients wiki_content.recipients
Chris@0 195 cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients)
Chris@0 196 subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :page => wiki_content.page.pretty_title)}"
Chris@0 197 body :wiki_content => wiki_content,
Chris@0 198 :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title),
Chris@0 199 :wiki_diff_url => url_for(:controller => 'wiki', :action => 'diff', :id => wiki_content.project, :page => wiki_content.page.title, :version => wiki_content.version)
Chris@0 200 render_multipart('wiki_content_updated', body)
Chris@0 201 end
Chris@0 202
Chris@0 203 # Builds a tmail object used to email the specified user their account information.
Chris@0 204 #
Chris@0 205 # Example:
Chris@0 206 # account_information(user, password) => tmail object
Chris@0 207 # Mailer.deliver_account_information(user, password) => sends account information to the user
Chris@0 208 def account_information(user, password)
Chris@0 209 set_language_if_valid user.language
Chris@0 210 recipients user.mail
Chris@0 211 subject l(:mail_subject_register, Setting.app_title)
Chris@0 212 body :user => user,
Chris@0 213 :password => password,
Chris@0 214 :login_url => url_for(:controller => 'account', :action => 'login')
Chris@0 215 render_multipart('account_information', body)
Chris@0 216 end
Chris@0 217
Chris@0 218 # Builds a tmail object used to email all active administrators of an account activation request.
Chris@0 219 #
Chris@0 220 # Example:
Chris@0 221 # account_activation_request(user) => tmail object
Chris@0 222 # Mailer.deliver_account_activation_request(user)=> sends an email to all active administrators
Chris@0 223 def account_activation_request(user)
Chris@0 224 # Send the email to all active administrators
Chris@0 225 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
Chris@0 226 subject l(:mail_subject_account_activation_request, Setting.app_title)
Chris@0 227 body :user => user,
Chris@0 228 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
Chris@0 229 render_multipart('account_activation_request', body)
Chris@0 230 end
Chris@0 231
Chris@0 232 # Builds a tmail object used to email the specified user that their account was activated by an administrator.
Chris@0 233 #
Chris@0 234 # Example:
Chris@0 235 # account_activated(user) => tmail object
Chris@0 236 # Mailer.deliver_account_activated(user) => sends an email to the registered user
Chris@0 237 def account_activated(user)
Chris@0 238 set_language_if_valid user.language
Chris@0 239 recipients user.mail
Chris@0 240 subject l(:mail_subject_register, Setting.app_title)
Chris@0 241 body :user => user,
Chris@0 242 :login_url => url_for(:controller => 'account', :action => 'login')
Chris@0 243 render_multipart('account_activated', body)
Chris@0 244 end
Chris@0 245
Chris@0 246 def lost_password(token)
Chris@0 247 set_language_if_valid(token.user.language)
Chris@0 248 recipients token.user.mail
Chris@0 249 subject l(:mail_subject_lost_password, Setting.app_title)
Chris@0 250 body :token => token,
Chris@0 251 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
Chris@0 252 render_multipart('lost_password', body)
Chris@0 253 end
Chris@0 254
Chris@0 255 def register(token)
Chris@0 256 set_language_if_valid(token.user.language)
Chris@0 257 recipients token.user.mail
Chris@0 258 subject l(:mail_subject_register, Setting.app_title)
Chris@0 259 body :token => token,
Chris@0 260 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
Chris@0 261 render_multipart('register', body)
Chris@0 262 end
Chris@0 263
Chris@0 264 def test(user)
Chris@0 265 set_language_if_valid(user.language)
Chris@0 266 recipients user.mail
Chris@0 267 subject 'Redmine test'
Chris@0 268 body :url => url_for(:controller => 'welcome')
Chris@0 269 render_multipart('test', body)
Chris@0 270 end
Chris@0 271
Chris@0 272 # Overrides default deliver! method to prevent from sending an email
Chris@0 273 # with no recipient, cc or bcc
Chris@0 274 def deliver!(mail = @mail)
Chris@0 275 set_language_if_valid @initial_language
Chris@0 276 return false if (recipients.nil? || recipients.empty?) &&
Chris@0 277 (cc.nil? || cc.empty?) &&
Chris@0 278 (bcc.nil? || bcc.empty?)
Chris@0 279
Chris@0 280 # Set Message-Id and References
Chris@0 281 if @message_id_object
Chris@0 282 mail.message_id = self.class.message_id_for(@message_id_object)
Chris@0 283 end
Chris@0 284 if @references_objects
Chris@0 285 mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
Chris@0 286 end
Chris@0 287
Chris@0 288 # Log errors when raise_delivery_errors is set to false, Rails does not
Chris@0 289 raise_errors = self.class.raise_delivery_errors
Chris@0 290 self.class.raise_delivery_errors = true
Chris@0 291 begin
Chris@0 292 return super(mail)
Chris@0 293 rescue Exception => e
Chris@0 294 if raise_errors
Chris@0 295 raise e
Chris@0 296 elsif mylogger
Chris@0 297 mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/email.yml."
Chris@0 298 end
Chris@0 299 ensure
Chris@0 300 self.class.raise_delivery_errors = raise_errors
Chris@0 301 end
Chris@0 302 end
Chris@0 303
Chris@0 304 # Sends reminders to issue assignees
Chris@0 305 # Available options:
Chris@0 306 # * :days => how many days in the future to remind about (defaults to 7)
Chris@0 307 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
Chris@0 308 # * :project => id or identifier of project to process (defaults to all projects)
Chris@0 309 def self.reminders(options={})
Chris@0 310 days = options[:days] || 7
Chris@0 311 project = options[:project] ? Project.find(options[:project]) : nil
Chris@0 312 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
Chris@0 313
Chris@0 314 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
Chris@0 315 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
Chris@0 316 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
Chris@0 317 s << "#{Issue.table_name}.project_id = #{project.id}" if project
Chris@0 318 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
Chris@0 319
Chris@0 320 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
Chris@0 321 :conditions => s.conditions
Chris@0 322 ).group_by(&:assigned_to)
Chris@0 323 issues_by_assignee.each do |assignee, issues|
Chris@0 324 deliver_reminder(assignee, issues, days) unless assignee.nil?
Chris@0 325 end
Chris@0 326 end
Chris@0 327
Chris@0 328 # Activates/desactivates email deliveries during +block+
Chris@0 329 def self.with_deliveries(enabled = true, &block)
Chris@0 330 was_enabled = ActionMailer::Base.perform_deliveries
Chris@0 331 ActionMailer::Base.perform_deliveries = !!enabled
Chris@0 332 yield
Chris@0 333 ensure
Chris@0 334 ActionMailer::Base.perform_deliveries = was_enabled
Chris@0 335 end
Chris@0 336
Chris@0 337 private
Chris@0 338 def initialize_defaults(method_name)
Chris@0 339 super
Chris@0 340 @initial_language = current_language
Chris@0 341 set_language_if_valid Setting.default_language
Chris@0 342 from Setting.mail_from
Chris@0 343
Chris@0 344 # Common headers
Chris@0 345 headers 'X-Mailer' => 'Redmine',
Chris@0 346 'X-Redmine-Host' => Setting.host_name,
Chris@0 347 'X-Redmine-Site' => Setting.app_title,
Chris@0 348 'Precedence' => 'bulk',
Chris@0 349 'Auto-Submitted' => 'auto-generated'
Chris@0 350 end
Chris@0 351
Chris@0 352 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
Chris@0 353 def redmine_headers(h)
Chris@0 354 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
Chris@0 355 end
Chris@0 356
Chris@0 357 # Overrides the create_mail method
Chris@0 358 def create_mail
Chris@0 359 # Removes the current user from the recipients and cc
Chris@0 360 # if he doesn't want to receive notifications about what he does
Chris@0 361 @author ||= User.current
Chris@0 362 if @author.pref[:no_self_notified]
Chris@0 363 recipients.delete(@author.mail) if recipients
Chris@0 364 cc.delete(@author.mail) if cc
Chris@0 365 end
Chris@0 366
Chris@0 367 notified_users = [recipients, cc].flatten.compact.uniq
Chris@0 368 # Rails would log recipients only, not cc and bcc
Chris@0 369 mylogger.info "Sending email notification to: #{notified_users.join(', ')}" if mylogger
Chris@0 370
Chris@0 371 # Blind carbon copy recipients
Chris@0 372 if Setting.bcc_recipients?
Chris@0 373 bcc(notified_users)
Chris@0 374 recipients []
Chris@0 375 cc []
Chris@0 376 end
Chris@0 377 super
Chris@0 378 end
Chris@0 379
Chris@0 380 # Rails 2.3 has problems rendering implicit multipart messages with
Chris@0 381 # layouts so this method will wrap an multipart messages with
Chris@0 382 # explicit parts.
Chris@0 383 #
Chris@0 384 # https://rails.lighthouseapp.com/projects/8994/tickets/2338-actionmailer-mailer-views-and-content-type
Chris@0 385 # https://rails.lighthouseapp.com/projects/8994/tickets/1799-actionmailer-doesnt-set-template_format-when-rendering-layouts
Chris@0 386
Chris@0 387 def render_multipart(method_name, body)
Chris@0 388 if Setting.plain_text_mail?
Chris@0 389 content_type "text/plain"
Chris@0 390 body render(:file => "#{method_name}.text.plain.rhtml", :body => body, :layout => 'mailer.text.plain.erb')
Chris@0 391 else
Chris@0 392 content_type "multipart/alternative"
Chris@0 393 part :content_type => "text/plain", :body => render(:file => "#{method_name}.text.plain.rhtml", :body => body, :layout => 'mailer.text.plain.erb')
Chris@0 394 part :content_type => "text/html", :body => render_message("#{method_name}.text.html.rhtml", body)
Chris@0 395 end
Chris@0 396 end
Chris@0 397
Chris@0 398 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
Chris@0 399 def self.controller_path
Chris@0 400 ''
Chris@0 401 end unless respond_to?('controller_path')
Chris@0 402
Chris@0 403 # Returns a predictable Message-Id for the given object
Chris@0 404 def self.message_id_for(object)
Chris@0 405 # id + timestamp should reduce the odds of a collision
Chris@0 406 # as far as we don't send multiple emails for the same object
Chris@0 407 timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
Chris@0 408 hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}"
Chris@0 409 host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
Chris@0 410 host = "#{::Socket.gethostname}.redmine" if host.empty?
Chris@0 411 "<#{hash}@#{host}>"
Chris@0 412 end
Chris@0 413
Chris@0 414 private
Chris@0 415
Chris@0 416 def message_id(object)
Chris@0 417 @message_id_object = object
Chris@0 418 end
Chris@0 419
Chris@0 420 def references(object)
Chris@0 421 @references_objects ||= []
Chris@0 422 @references_objects << object
Chris@0 423 end
Chris@0 424
Chris@0 425 def mylogger
Chris@0 426 RAILS_DEFAULT_LOGGER
Chris@0 427 end
Chris@0 428 end
Chris@0 429
Chris@0 430 # Patch TMail so that message_id is not overwritten
Chris@0 431 module TMail
Chris@0 432 class Mail
Chris@0 433 def add_message_id( fqdn = nil )
Chris@0 434 self.message_id ||= ::TMail::new_message_id(fqdn)
Chris@0 435 end
Chris@0 436 end
Chris@0 437 end