mailer.rb 22.3 KB
Newer Older
1
# Redmine - project management software
jplang's avatar
jplang committed
2
# Copyright (C) 2006-2017  Jean-Philippe Lang
3 4 5 6 7
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
8
#
9 10 11 12
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
13
#
14 15 16 17
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

18 19
require 'roadie'

20
class Mailer < ActionMailer::Base
edavis10's avatar
edavis10 committed
21
  layout 'mailer'
22 23 24
  helper :application
  helper :issues
  helper :custom_fields
25

26
  include Redmine::I18n
27
  include Roadie::Rails::Automatic
28

29
  def self.default_url_options
30 31 32 33 34 35 36 37 38 39
    options = {:protocol => Setting.protocol}
    if Setting.host_name.to_s =~ /\A(https?\:\/\/)?(.+?)(\:(\d+))?(\/.+)?\z/i
      host, port, prefix = $2, $4, $5
      options.merge!({
        :host => host, :port => port, :script_name => prefix
      })
    else
      options[:host] = Setting.host_name
    end
    options
40
  end
41

42 43
  # Builds a mail for notifying to_users and cc_users about a new issue
  def issue_add(issue, to_users, cc_users)
44 45 46 47
    redmine_headers 'Project' => issue.project.identifier,
                    'Issue-Id' => issue.id,
                    'Issue-Author' => issue.author.login
    redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
48
    message_id issue
49
    references issue
50
    @author = issue.author
jplang's avatar
jplang committed
51
    @issue = issue
52
    @users = to_users + cc_users
jplang's avatar
jplang committed
53
    @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue)
54 55
    mail :to => to_users,
      :cc => cc_users,
jplang's avatar
jplang committed
56
      :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
57 58
  end

59 60 61 62 63
  # Notifies users about a new issue
  def self.deliver_issue_add(issue)
    to = issue.notified_users
    cc = issue.notified_watchers - to
    issue.each_notification(to + cc) do |users|
64
      issue_add(issue, to & users, cc & users).deliver
65 66 67 68 69 70
    end
  end

  # Builds a mail for notifying to_users and cc_users about an issue update
  def issue_edit(journal, to_users, cc_users)
    issue = journal.journalized
71 72 73 74
    redmine_headers 'Project' => issue.project.identifier,
                    'Issue-Id' => issue.id,
                    'Issue-Author' => issue.author.login
    redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
75 76
    message_id journal
    references issue
77
    @author = journal.user
78 79 80
    s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
    s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
    s << issue.subject
jplang's avatar
jplang committed
81
    @issue = issue
82
    @users = to_users + cc_users
jplang's avatar
jplang committed
83
    @journal = journal
84
    @journal_details = journal.visible_details(@users.first)
jplang's avatar
jplang committed
85
    @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}")
86 87
    mail :to => to_users,
      :cc => cc_users,
jplang's avatar
jplang committed
88
      :subject => s
89
  end
90

91 92 93 94
  # Notifies users about an issue update
  def self.deliver_issue_edit(journal)
    issue = journal.journalized.reload
    to = journal.notified_users
95
    cc = journal.notified_watchers - to
96 97
    journal.each_notification(to + cc) do |users|
      issue.each_notification(users) do |users2|
98
        issue_edit(journal, to & users2, cc & users2).deliver
99
      end
100 101 102
    end
  end

103 104
  def reminder(user, issues, days)
    set_language_if_valid user.language
jplang's avatar
jplang committed
105 106 107
    @issues = issues
    @days = days
    @issues_url = url_for(:controller => 'issues', :action => 'index',
108 109
                                :set_filter => 1, :assigned_to_id => user.id,
                                :sort => 'due_date:asc')
110
    mail :to => user,
jplang's avatar
jplang committed
111
      :subject => l(:mail_subject_reminder, :count => issues.size, :days => days)
112
  end
113

114
  # Builds a Mail::Message object used to email users belonging to the added document's project.
115 116
  #
  # Example:
117 118
  #   document_added(document) => Mail::Message object
  #   Mailer.document_added(document).deliver => sends an email to the document's project recipients
119
  def document_added(document)
120
    redmine_headers 'Project' => document.project.identifier
121
    @author = User.current
jplang's avatar
jplang committed
122 123
    @document = document
    @document_url = url_for(:controller => 'documents', :action => 'show', :id => document)
124
    mail :to => document.notified_users,
jplang's avatar
jplang committed
125
      :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
126
  end
127

128
  # Builds a Mail::Message object used to email recipients of a project when an attachements are added.
129 130
  #
  # Example:
131 132
  #   attachments_added(attachments) => Mail::Message object
  #   Mailer.attachments_added(attachments).deliver => sends an email to the project's recipients
133
  def attachments_added(attachments)
134
    container = attachments.first.container
135
    added_to = ''
jplang's avatar
Mailer:  
jplang committed
136
    added_to_url = ''
137
    @author = attachments.first.author
138
    case container.class.name
139
    when 'Project'
140
      added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
141
      added_to = "#{l(:label_project)}: #{container}"
142
      recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
143
    when 'Version'
144
      added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
145
      added_to = "#{l(:label_version)}: #{container.name}"
146
      recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
147
    when 'Document'
jplang's avatar
Mailer:  
jplang committed
148
      added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
149
      added_to = "#{l(:label_document)}: #{container.title}"
150
      recipients = container.notified_users
151
    end
152
    redmine_headers 'Project' => container.project.identifier
jplang's avatar
jplang committed
153 154 155 156 157
    @attachments = attachments
    @added_to = added_to
    @added_to_url = added_to_url
    mail :to => recipients,
      :subject => "[#{container.project.name}] #{l(:label_attachment_new)}"
158
  end
159

160
  # Builds a Mail::Message object used to email recipients of a news' project when a news item is added.
161 162
  #
  # Example:
163 164
  #   news_added(news) => Mail::Message object
  #   Mailer.news_added(news).deliver => sends an email to the news' project recipients
165
  def news_added(news)
166
    redmine_headers 'Project' => news.project.identifier
167
    @author = news.author
168
    message_id news
169
    references news
jplang's avatar
jplang committed
170 171
    @news = news
    @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
172 173
    mail :to => news.notified_users,
      :cc => news.notified_watchers_for_added_news,
jplang's avatar
jplang committed
174
      :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
175
  end
176

177
  # Builds a Mail::Message object used to email recipients of a news' project when a news comment is added.
178 179
  #
  # Example:
180
  #   news_comment_added(comment) => Mail::Message object
181 182 183 184
  #   Mailer.news_comment_added(comment) => sends an email to the news' project recipients
  def news_comment_added(comment)
    news = comment.commented
    redmine_headers 'Project' => news.project.identifier
185
    @author = comment.author
186
    message_id comment
187
    references news
jplang's avatar
jplang committed
188 189 190
    @news = news
    @comment = comment
    @news_url = url_for(:controller => 'news', :action => 'show', :id => news)
191 192
    mail :to => news.notified_users,
     :cc => news.notified_watchers,
jplang's avatar
jplang committed
193
     :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
194
  end
jplang's avatar
Mailer:  
jplang committed
195

196
  # Builds a Mail::Message object used to email the recipients of the specified message that was posted.
197 198
  #
  # Example:
199 200
  #   message_posted(message) => Mail::Message object
  #   Mailer.message_posted(message).deliver => sends an email to the recipients
201
  def message_posted(message)
202 203
    redmine_headers 'Project' => message.project.identifier,
                    'Topic-Id' => (message.parent_id || message.id)
204
    @author = message.author
205
    message_id message
206
    references message.root
207 208
    recipients = message.notified_users
    cc = ((message.root.notified_watchers + message.board.notified_watchers).uniq - recipients)
jplang's avatar
jplang committed
209 210 211 212 213
    @message = message
    @message_url = url_for(message.event_url)
    mail :to => recipients,
      :cc => cc,
      :subject => "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
jplang's avatar
Mailer:  
jplang committed
214
  end
215

216
  # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was added.
217 218
  #
  # Example:
219 220
  #   wiki_content_added(wiki_content) => Mail::Message object
  #   Mailer.wiki_content_added(wiki_content).deliver => sends an email to the project's recipients
221 222 223
  def wiki_content_added(wiki_content)
    redmine_headers 'Project' => wiki_content.project.identifier,
                    'Wiki-Page-Id' => wiki_content.page.id
224
    @author = wiki_content.author
225
    message_id wiki_content
226 227
    recipients = wiki_content.notified_users
    cc = wiki_content.page.wiki.notified_watchers - recipients
jplang's avatar
jplang committed
228 229
    @wiki_content = wiki_content
    @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
230 231
                                      :project_id => wiki_content.project,
                                      :id => wiki_content.page.title)
jplang's avatar
jplang committed
232 233 234
    mail :to => recipients,
      :cc => cc,
      :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}"
235
  end
236

237
  # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was updated.
238 239
  #
  # Example:
240 241
  #   wiki_content_updated(wiki_content) => Mail::Message object
  #   Mailer.wiki_content_updated(wiki_content).deliver => sends an email to the project's recipients
242 243 244
  def wiki_content_updated(wiki_content)
    redmine_headers 'Project' => wiki_content.project.identifier,
                    'Wiki-Page-Id' => wiki_content.page.id
245
    @author = wiki_content.author
246
    message_id wiki_content
247 248
    recipients = wiki_content.notified_users
    cc = wiki_content.page.wiki.notified_watchers + wiki_content.page.notified_watchers - recipients
jplang's avatar
jplang committed
249 250
    @wiki_content = wiki_content
    @wiki_content_url = url_for(:controller => 'wiki', :action => 'show',
251
                                      :project_id => wiki_content.project,
jplang's avatar
jplang committed
252 253
                                      :id => wiki_content.page.title)
    @wiki_diff_url = url_for(:controller => 'wiki', :action => 'diff',
254 255
                                   :project_id => wiki_content.project, :id => wiki_content.page.title,
                                   :version => wiki_content.version)
jplang's avatar
jplang committed
256 257 258
    mail :to => recipients,
      :cc => cc,
      :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}"
259
  end
260

261
  # Builds a Mail::Message object used to email the specified user their account information.
262 263
  #
  # Example:
264 265
  #   account_information(user, password) => Mail::Message object
  #   Mailer.account_information(user, password).deliver => sends account information to the user
jplang's avatar
Mailer:  
jplang committed
266 267
  def account_information(user, password)
    set_language_if_valid user.language
jplang's avatar
jplang committed
268 269 270 271 272
    @user = user
    @password = password
    @login_url = url_for(:controller => 'account', :action => 'login')
    mail :to => user.mail,
      :subject => l(:mail_subject_register, Setting.app_title)
jplang's avatar
Mailer:  
jplang committed
273
  end
274

275
  # Builds a Mail::Message object used to email all active administrators of an account activation request.
276 277
  #
  # Example:
278 279
  #   account_activation_request(user) => Mail::Message object
  #   Mailer.account_activation_request(user).deliver => sends an email to all active administrators
280 281
  def account_activation_request(user)
    # Send the email to all active administrators
282
    recipients = User.active.where(:admin => true)
jplang's avatar
jplang committed
283 284
    @user = user
    @url = url_for(:controller => 'users', :action => 'index',
285 286
                         :status => User::STATUS_REGISTERED,
                         :sort_key => 'created_on', :sort_order => 'desc')
jplang's avatar
jplang committed
287 288
    mail :to => recipients,
      :subject => l(:mail_subject_account_activation_request, Setting.app_title)
289
  end
jplang's avatar
Mailer:  
jplang committed
290

291
  # Builds a Mail::Message object used to email the specified user that their account was activated by an administrator.
292 293
  #
  # Example:
294 295
  #   account_activated(user) => Mail::Message object
  #   Mailer.account_activated(user).deliver => sends an email to the registered user
296 297
  def account_activated(user)
    set_language_if_valid user.language
jplang's avatar
jplang committed
298 299 300 301
    @user = user
    @login_url = url_for(:controller => 'account', :action => 'login')
    mail :to => user.mail,
      :subject => l(:mail_subject_register, Setting.app_title)
302 303
  end

304
  def lost_password(token, recipient=nil)
305
    set_language_if_valid(token.user.language)
306
    recipient ||= token.user.mail
jplang's avatar
jplang committed
307 308
    @token = token
    @url = url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
309
    mail :to => recipient,
jplang's avatar
jplang committed
310
      :subject => l(:mail_subject_lost_password, Setting.app_title)
311
  end
312

313
  # Notifies user that his password was updated
314
  def self.password_updated(user, options={})
315 316 317 318 319
    # Don't send a notification to the dummy email address when changing the password
    # of the default admin account which is required after the first login
    # TODO: maybe not the best way to handle this
    return if user.admin? && user.login == 'admin' && user.mail == 'admin@example.net'

320
    security_notification(user,
321
      message: :mail_body_password_updated,
322
      title: :button_change_password,
323 324
      remote_ip: options[:remote_ip],
      originator: user,
325 326 327 328
      url: {controller: 'my', action: 'password'}
    ).deliver
  end

329 330
  def register(token)
    set_language_if_valid(token.user.language)
jplang's avatar
jplang committed
331 332 333 334
    @token = token
    @url = url_for(:controller => 'account', :action => 'activate', :token => token.value)
    mail :to => token.user.mail,
      :subject => l(:mail_subject_register, Setting.app_title)
335
  end
336

337 338 339 340 341 342 343 344
  def security_notification(recipients, options={})
    @user = Array(recipients).detect{|r| r.is_a? User }
    set_language_if_valid(@user.try :language)
    @message = l(options[:message],
      field: (options[:field] && l(options[:field])),
      value: options[:value]
    )
    @title = options[:title] && l(options[:title])
345 346
    @originator = options[:originator] || User.current
    @remote_ip = options[:remote_ip] || @originator.remote_ip
347
    @url = options[:url] && (options[:url].is_a?(Hash) ? url_for(options[:url]) : options[:url])
348 349
    redmine_headers 'Sender' => @originator.login
    redmine_headers 'Url' => @url
350
    mail :to => recipients,
351
      :subject => "[#{Setting.app_title}] #{l(:mail_subject_security_notification)}"
352 353
  end

354 355 356 357 358
  def settings_updated(recipients, changes)
    redmine_headers 'Sender' => User.current.login
    @changes = changes
    @url = url_for(controller: 'settings', action: 'index')
    mail :to => recipients,
359
      :subject => "[#{Setting.app_title}] #{l(:mail_subject_security_notification)}"
360 361
  end

362
  # Notifies admins about settings changes
363 364 365 366
  def self.security_settings_updated(changes)
    return unless changes.present?

    users = User.active.where(admin: true).to_a
367
    settings_updated(users, changes).deliver
368 369
  end

370
  def test_email(user)
371
    set_language_if_valid(user.language)
jplang's avatar
jplang committed
372 373 374
    @url = url_for(:controller => 'welcome')
    mail :to => user.mail,
      :subject => 'Redmine test'
jplang's avatar
Mailer:  
jplang committed
375
  end
376

377 378 379 380 381
  # Sends reminders to issue assignees
  # Available options:
  # * :days     => how many days in the future to remind about (defaults to 7)
  # * :tracker  => id of tracker for filtering issues (defaults to all trackers)
  # * :project  => id or identifier of project to process (defaults to all projects)
382
  # * :users    => array of user/group ids who should be reminded
383
  # * :version  => name of target version for filtering issues (defaults to none)
384 385 386 387
  def self.reminders(options={})
    days = options[:days] || 7
    project = options[:project] ? Project.find(options[:project]) : nil
    tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
388 389
    target_version_id = options[:version] ? Version.named(options[:version]).pluck(:id) : nil
    if options[:version] && target_version_id.blank?
390
      raise ActiveRecord::RecordNotFound.new("Couldn't find Version named #{options[:version]}")
391
    end
392
    user_ids = options[:users]
393

jplang's avatar
jplang committed
394
    scope = Issue.open.where("#{Issue.table_name}.assigned_to_id IS NOT NULL" +
jplang's avatar
jplang committed
395
      " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" +
jplang's avatar
jplang committed
396
      " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date
jplang's avatar
jplang committed
397
    )
jplang's avatar
jplang committed
398 399
    scope = scope.where(:assigned_to_id => user_ids) if user_ids.present?
    scope = scope.where(:project_id => project.id) if project
400
    scope = scope.where(:fixed_version_id => target_version_id) if target_version_id.present?
jplang's avatar
jplang committed
401
    scope = scope.where(:tracker_id => tracker.id) if tracker
402 403
    issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).
                              group_by(&:assigned_to)
404 405 406 407 408 409 410 411 412
    issues_by_assignee.keys.each do |assignee|
      if assignee.is_a?(Group)
        assignee.users.each do |user|
          issues_by_assignee[user] ||= []
          issues_by_assignee[user] += issues_by_assignee[assignee]
        end
      end
    end

413
    issues_by_assignee.each do |assignee, issues|
414 415 416 417
      if assignee.is_a?(User) && assignee.active? && issues.present?
        visible_issues = issues.select {|i| i.visible?(assignee)}
        reminder(assignee, visible_issues, days).deliver if visible_issues.present?
      end
418 419
    end
  end
420

421 422 423 424 425 426 427 428
  # Activates/desactivates email deliveries during +block+
  def self.with_deliveries(enabled = true, &block)
    was_enabled = ActionMailer::Base.perform_deliveries
    ActionMailer::Base.perform_deliveries = !!enabled
    yield
  ensure
    ActionMailer::Base.perform_deliveries = was_enabled
  end
429

430 431 432 433
  # Sends emails synchronously in the given block
  def self.with_synched_deliveries(&block)
    saved_method = ActionMailer::Base.delivery_method
    if m = saved_method.to_s.match(%r{^async_(.+)$})
434 435 436
      synched_method = m[1]
      ActionMailer::Base.delivery_method = synched_method.to_sym
      ActionMailer::Base.send "#{synched_method}_settings=", ActionMailer::Base.send("async_#{synched_method}_settings")
437 438 439 440 441 442
    end
    yield
  ensure
    ActionMailer::Base.delivery_method = saved_method
  end

443
  def mail(headers={}, &block)
444
    headers.reverse_merge! 'X-Mailer' => 'Redmine',
445
            'X-Redmine-Host' => Setting.host_name,
446
            'X-Redmine-Site' => Setting.app_title,
447
            'X-Auto-Response-Suppress' => 'All',
jplang's avatar
jplang committed
448
            'Auto-Submitted' => 'auto-generated',
449 450
            'From' => Setting.mail_from,
            'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>"
451

452 453 454 455 456 457 458
    # Replaces users with their email addresses
    [:to, :cc, :bcc].each do |key|
      if headers[key].present?
        headers[key] = self.class.email_addresses(headers[key])
      end
    end

459
    # Removes the author from the recipients and cc
460 461
    # if the author does not want to receive notifications
    # about what the author do
462
    if @author && @author.logged? && @author.pref.no_self_notified
463 464 465
      addresses = @author.mails
      headers[:to] -= addresses if headers[:to].is_a?(Array)
      headers[:cc] -= addresses if headers[:cc].is_a?(Array)
466
    end
467

468
    if @author && @author.logged?
469 470 471
      redmine_headers 'Sender' => @author.login
    end

472 473
    # Blind carbon copy recipients
    if Setting.bcc_recipients?
jplang's avatar
jplang committed
474 475 476 477 478 479 480 481 482
      headers[:bcc] = [headers[:to], headers[:cc]].flatten.uniq.reject(&:blank?)
      headers[:to] = nil
      headers[:cc] = nil
    end

    if @message_id_object
      headers[:message_id] = "<#{self.class.message_id_for(@message_id_object)}>"
    end
    if @references_objects
483
      headers[:references] = @references_objects.collect {|o| "<#{self.class.references_for(o)}>"}.join(' ')
jplang's avatar
jplang committed
484 485
    end

486 487 488 489 490 491 492
    m = if block_given?
      super headers, &block
    else
      super headers do |format|
        format.text
        format.html unless Setting.plain_text_mail?
      end
493
    end
jplang's avatar
jplang committed
494
    set_language_if_valid @initial_language
495 496

    m
jplang's avatar
jplang committed
497 498 499 500 501 502 503
  end

  def initialize(*args)
    @initial_language = current_language
    set_language_if_valid Setting.default_language
    super
  end
504

jplang's avatar
jplang committed
505 506
  def self.deliver_mail(mail)
    return false if mail.to.blank? && mail.cc.blank? && mail.bcc.blank?
jplang's avatar
jplang committed
507 508 509 510 511 512 513 514 515 516 517
    begin
      # Log errors when raise_delivery_errors is set to false, Rails does not
      mail.raise_delivery_errors = true
      super
    rescue Exception => e
      if ActionMailer::Base.raise_delivery_errors
        raise e
      else
        Rails.logger.error "Email delivery error: #{e.message}"
      end
    end
518
  end
519

jplang's avatar
jplang committed
520 521
  def self.method_missing(method, *args, &block)
    if m = method.to_s.match(%r{^deliver_(.+)$})
522
      ActiveSupport::Deprecation.warn "Mailer.deliver_#{m[1]}(*args) is deprecated. Use Mailer.#{m[1]}(*args).deliver instead."
jplang's avatar
jplang committed
523
      send(m[1], *args).deliver
524
    else
jplang's avatar
jplang committed
525
      super
526
    end
nbc's avatar
nbc committed
527
  end
528

529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
  # Returns an array of email addresses to notify by
  # replacing users in arg with their notified email addresses
  #
  # Example:
  #   Mailer.email_addresses(users)
  #   => ["foo@example.net", "bar@example.net"]
  def self.email_addresses(arg)
    arr = Array.wrap(arg)
    mails = arr.reject {|a| a.is_a? Principal}
    users = arr - mails
    if users.any?
      mails += EmailAddress.
        where(:user_id => users.map(&:id)).
        where("is_default = ? OR notify = ?", true, true).
        pluck(:address)
    end
    mails
  end

jplang's avatar
jplang committed
548 549 550 551 552 553
  private

  # Appends a Redmine header field (name is prepended with 'X-Redmine-')
  def redmine_headers(h)
    h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s }
  end
554

555
  def self.token_for(object, rand=true)
556
    timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on)
557 558 559 560 561 562 563 564
    hash = [
      "redmine",
      "#{object.class.name.demodulize.underscore}-#{object.id}",
      timestamp.strftime("%Y%m%d%H%M%S")
    ]
    if rand
      hash << Redmine::Utils.random_hex(8)
    end
565
    host = Setting.mail_from.to_s.strip.gsub(%r{^.*@|>}, '')
566
    host = "#{::Socket.gethostname}.redmine" if host.empty?
567 568 569 570 571 572 573 574 575 576 577 578
    "#{hash.join('.')}@#{host}"
  end

  # Returns a Message-Id for the given object
  def self.message_id_for(object)
    token_for(object, true)
  end

  # Returns a uniq token for a given object referenced by all notifications
  # related to this object
  def self.references_for(object)
    token_for(object, false)
579
  end
580

581 582 583
  def message_id(object)
    @message_id_object = object
  end
584

585 586 587 588 589
  def references(object)
    @references_objects ||= []
    @references_objects << object
  end
end