aboutsummaryrefslogtreecommitdiffstats
path: root/actionmailer/lib/action_mailer/old_api.rb
blob: 79d024a3505d458ea1ecee772b8b684ba705a87c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
require 'active_support/concern'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/blank'

module ActionMailer
  module OldApi #:nodoc:
    extend ActiveSupport::Concern

    included do
      extend ActionMailer::AdvAttrAccessor

      @@protected_instance_variables = %w(@parts)
      cattr_reader :protected_instance_variables

      # Specify the BCC addresses for the message
      adv_attr_accessor :bcc

      # Specify the CC addresses for the message.
      adv_attr_accessor :cc

      # Specify the charset to use for the message. This defaults to the
      # +default_charset+ specified for ActionMailer::Base.
      adv_attr_accessor :charset

      # Specify the content type for the message. This defaults to <tt>text/plain</tt>
      # in most cases, but can be automatically set in some situations.
      adv_attr_accessor :content_type

      # Specify the from address for the message.
      adv_attr_accessor :from

      # Specify the address (if different than the "from" address) to direct
      # replies to this message.
      adv_attr_accessor :reply_to

      # Specify the order in which parts should be sorted, based on content-type.
      # This defaults to the value for the +default_implicit_parts_order+.
      adv_attr_accessor :implicit_parts_order

      # Defaults to "1.0", but may be explicitly given if needed.
      adv_attr_accessor :mime_version

      # The recipient addresses for the message, either as a string (for a single
      # address) or an array (for multiple addresses).
      adv_attr_accessor :recipients

      # The date on which the message was sent. If not set (the default), the
      # header will be set by the delivery agent.
      adv_attr_accessor :sent_on

      # Specify the subject of the message.
      adv_attr_accessor :subject

      # Specify the template name to use for current message. This is the "base"
      # template name, without the extension or directory, and may be used to
      # have multiple mailer methods share the same template.
      adv_attr_accessor :template

      # Override the mailer name, which defaults to an inflected version of the
      # mailer's class name. If you want to use a template in a non-standard
      # location, you can use this to specify that location.
      adv_attr_accessor :mailer_name

      # Define the body of the message. This is either a Hash (in which case it
      # specifies the variables to pass to the template when it is rendered),
      # or a string, in which case it specifies the actual text of the message.
      adv_attr_accessor :body

      # Alias controller_path to mailer_name so render :partial in views work.
      alias :controller_path :mailer_name
    end

    def process(method_name, *args)
      initialize_defaults(method_name)
      super
      unless @mail_was_called
        create_parts
        create_mail
      end
      @_message
    end

    # Add a part to a multipart message, with the given content-type. The
    # part itself is yielded to the block so that other properties (charset,
    # body, headers, etc.) can be set on it.
    def part(params)
      params = {:content_type => params} if String === params

      if custom_headers = params.delete(:headers)
        params.merge!(custom_headers)
      end

      part = Mail::Part.new(params)

      yield part if block_given?
      @parts << part
    end

    # Add an attachment to a multipart message. This is simply a part with the
    # content-disposition set to "attachment".
    def attachment(params, &block)
      params = { :content_type => params } if String === params

      params[:content] ||= params.delete(:data) || params.delete(:body)

      if params[:filename]
        params = normalize_file_hash(params)
      else
        params = normalize_nonfile_hash(params)
      end

      part(params, &block)
    end

  protected

    def normalize_nonfile_hash(params)
      content_disposition = "attachment;"
      
      mime_type = params.delete(:mime_type)
      
      if content_type = params.delete(:content_type)
        content_type = "#{mime_type || content_type};"
      end

      params[:body] = params.delete(:data) if params[:data]
      
      { :content_type => content_type,
        :content_disposition => content_disposition }.merge(params)
    end
    
    def normalize_file_hash(params)
      filename = File.basename(params.delete(:filename))
      content_disposition = "attachment; filename=\"#{File.basename(filename)}\""
      
      mime_type = params.delete(:mime_type)
      
      if (content_type = params.delete(:content_type)) && (content_type !~ /filename=/)
        content_type = "#{mime_type || content_type}; filename=\"#{filename}\""
      end
      
      params[:body] = params.delete(:data) if params[:data]
      
      { :content_type => content_type,
        :content_disposition => content_disposition }.merge(params)
    end

    def create_mail 
      m = @_message

      set_fields!({:subject => subject, :to => recipients, :from => from,
                   :bcc => bcc, :cc => cc, :reply_to => reply_to}, charset)

      m.mime_version = mime_version    unless mime_version.nil?
      m.date         = sent_on.to_time rescue sent_on if sent_on

      @headers.each { |k, v| m[k] = v }

      real_content_type, ctype_attrs = parse_content_type
      main_type, sub_type = split_content_type(real_content_type)

      if @parts.size == 1 && @parts.first.parts.empty?
        m.content_type([main_type, sub_type, ctype_attrs])
        m.body = @parts.first.body.encoded
      else
        @parts.each do |p|
          m.add_part(p)
        end

        m.body.set_sort_order(@implicit_parts_order)
        m.body.sort_parts!

        if real_content_type =~ /multipart/
          ctype_attrs.delete "charset"
          m.content_type([main_type, sub_type, ctype_attrs])
        end
      end

      wrap_delivery_behavior!
      m.content_transfer_encoding = '8bit' unless m.body.only_us_ascii?
      
      @_message
    end
    
    # Set up the default values for the various instance variables of this
    # mailer. Subclasses may override this method to provide different
    # defaults.
    def initialize_defaults(method_name) 
      @charset              ||= self.class.default[:charset].try(:dup)
      @content_type         ||= self.class.default[:content_type].try(:dup)
      @implicit_parts_order ||= self.class.default[:parts_order].try(:dup)
      @mime_version         ||= self.class.default[:mime_version].try(:dup)

      @mailer_name   ||= self.class.mailer_name.dup
      @template      ||= method_name
      @mail_was_called = false

      @parts   ||= []
      @headers ||= {}
      @sent_on ||= Time.now
      @body ||= {}
    end

    def create_parts 
      if String === @body
        @parts.unshift create_inline_part(@body)
      elsif @parts.empty? || @parts.all? { |p| p.content_disposition =~ /^attachment/ }
        lookup_context.find_all(@template, @mailer_name).each do |template|
          self.formats = template.formats
          @parts << create_inline_part(render(:template => template), template.mime_type)
        end

        if @parts.size > 1
          @content_type = "multipart/alternative" if @content_type !~ /^multipart/
        end

        # If this is a multipart e-mail add the mime_version if it is not
        # already set.
        @mime_version ||= "1.0" if !@parts.empty?
      end
    end

    def create_inline_part(body, mime_type=nil) 
      ct = mime_type || "text/plain"
      main_type, sub_type = split_content_type(ct.to_s)

      Mail::Part.new(
        :content_type => [main_type, sub_type, {:charset => charset}],
        :content_disposition => "inline",
        :body => body
      )
    end

    def set_fields!(headers, charset) #:nodoc:
      m = @_message
      m.charset = charset
      m.subject  ||= headers.delete(:subject)  if headers[:subject]
      m.to       ||= headers.delete(:to)       if headers[:to]
      m.from     ||= headers.delete(:from)     if headers[:from]
      m.cc       ||= headers.delete(:cc)       if headers[:cc]
      m.bcc      ||= headers.delete(:bcc)      if headers[:bcc]
      m.reply_to ||= headers.delete(:reply_to) if headers[:reply_to]
    end

    def split_content_type(ct) 
      ct.to_s.split("/")
    end

    def parse_content_type(defaults=nil) 
      if @content_type.blank?
        [ nil, {} ]
      else
        ctype, *attrs = @content_type.split(/;\s*/)
        attrs = attrs.inject({}) { |h,s| k,v = s.split(/\=/, 2); h[k] = v; h }
        [ctype, {"charset" => @charset}.merge(attrs)]
      end
    end
  end
end