File: app/models/mailer.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  class: Mailer#18
includes
  UrlWriter ( ActionController )
  I18n ( Redmine )
inherits from
  Base ( ActionMailer )
has properties
class method: default_url_options #27
method: issue_add / 1 #38
method: issue_edit / 1 #58
method: reminder / 3 #81
method: document_added / 1 #98
method: attachments_added / 1 #113
method: news_added / 1 #145
method: news_comment_added / 1 #161
method: message_posted / 1 #180
method: wiki_content_added / 1 #199
method: wiki_content_updated / 1 #219
method: account_information / 2 #242
method: account_activation_request / 1 #257
method: account_activated / 1 #273
method: lost_password / 1 #282
method: register / 1 #291
method: test_email / 1 #300
method: deliver! / 1 #310
class method: reminders / 1 #346
class method: with_deliveries / 2 #367
class method: with_synched_deliveries / 1 #376
method: initialize_defaults / 1 #387
method: redmine_headers / 1 #402
method: create_mail #407
method: render_multipart / 2 #443
class method: controller_path #460
class method: message_id_for / 1 #465
method: message_id / 1 #477
method: references / 1 #481
method: mylogger #486
  module: TMail#492
  class: Mail#493
inherits from
  Object ( Builtin-Module )
has properties
method: add_message_id / 1 #494

Class Hierarchy

Object ( Builtin-Module )
Base ( ActionMailer )
  Mailer    #18
Mail ( TMail ) — #493

Code

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