diff options
Diffstat (limited to 'actionmailer/lib/action_mailer')
-rw-r--r-- | actionmailer/lib/action_mailer/base.rb | 448 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/collector.rb | 36 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/delivery_method.rb | 56 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/delivery_method/file.rb | 21 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/delivery_method/sendmail.rb | 22 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/delivery_method/smtp.rb | 30 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/delivery_method/test.rb | 12 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/delivery_methods.rb | 87 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/deprecated_api.rb | 112 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/deprecated_body.rb | 46 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/mail_helper.rb | 7 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/old_api.rb | 250 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/test_case.rb | 2 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/test_helper.rb | 1 | ||||
-rw-r--r-- | actionmailer/lib/action_mailer/tmail_compat.rb | 12 |
15 files changed, 638 insertions, 504 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 356861b591..7101bfbb70 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,6 +1,8 @@ require 'active_support/core_ext/class' +require 'active_support/core_ext/module/delegation' require 'mail' require 'action_mailer/tmail_compat' +require 'action_mailer/collector' module ActionMailer #:nodoc: # Action Mailer allows you to send email from your application using a mailer model and views. @@ -251,8 +253,8 @@ module ActionMailer #:nodoc: # and appear last in the mime encoded message. You can also pick a different order from inside a method with # +implicit_parts_order+. class Base < AbstractController::Base - include Quoting - extend AdvAttrAccessor + include DeliveryMethods, Quoting + abstract! include AbstractController::Logger include AbstractController::Rendering @@ -262,27 +264,20 @@ module ActionMailer #:nodoc: include AbstractController::UrlFor helper ActionMailer::MailHelper - include ActionMailer::DeprecatedBody - private_class_method :new #:nodoc: - - @@raise_delivery_errors = true - cattr_accessor :raise_delivery_errors - - @@perform_deliveries = true - cattr_accessor :perform_deliveries + include ActionMailer::OldApi + include ActionMailer::DeprecatedApi - @@deliveries = [] - cattr_accessor :deliveries + private_class_method :new #:nodoc: - @@default_charset = "utf-8" - cattr_accessor :default_charset + extlib_inheritable_accessor :default_charset + self.default_charset = "utf-8" - @@default_content_type = "text/plain" - cattr_accessor :default_content_type + extlib_inheritable_accessor :default_content_type + self.default_content_type = "text/plain" - @@default_mime_version = "1.0" - cattr_accessor :default_mime_version + extlib_inheritable_accessor :default_mime_version + self.default_mime_version = "1.0" # This specifies the order that the parts of a multipart email will be. Usually you put # text/plain at the top so someone without a MIME capable email reader can read the plain @@ -290,103 +285,16 @@ module ActionMailer #:nodoc: # # Any content type that is not listed here will be inserted in the order you add them to # the email after the content types you list here. - @@default_implicit_parts_order = [ "text/plain", "text/enriched", "text/html" ] - cattr_accessor :default_implicit_parts_order - - @@protected_instance_variables = %w(@parts @mail) - 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 additional headers to be added to the message. - adv_attr_accessor :headers - - # 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 - - # Expose the internal mail - attr_reader :mail - - # Alias controller_path to mailer_name so render :partial in views work. - alias :controller_path :mailer_name + extlib_inheritable_accessor :default_implicit_parts_order + self.default_implicit_parts_order = [ "text/plain", "text/enriched", "text/html" ] class << self - attr_writer :mailer_name - - delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::File, :prefix => :file - delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::Sendmail, :prefix => :sendmail - delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::Smtp, :prefix => :smtp - def mailer_name @mailer_name ||= name.underscore end + attr_writer :mailer_name alias :controller_path :mailer_name - def delivery_method=(method_name) - @delivery_method = ActionMailer::DeliveryMethod.lookup_method(method_name) - end - - def respond_to?(method_symbol, include_private = false) #:nodoc: - matches_dynamic_method?(method_symbol) || super - end - - def method_missing(method_symbol, *parameters) #:nodoc: - if match = matches_dynamic_method?(method_symbol) - case match[1] - when 'create' then new(match[2], *parameters).mail - when 'deliver' then new(match[2], *parameters).deliver! - when 'new' then nil - else super - end - else - super - end - end - # Receives a raw email, parses it into an email object, decodes it, # instantiates a new mailer, and passes the email object to the mailer # object's +receive+ method. If you want your mailer to be able to @@ -406,26 +314,21 @@ module ActionMailer #:nodoc: end end - # Deliver the given mail object directly. This can be used to deliver - # a preconstructed mail object, like: - # - # email = MyMailer.create_some_mail(parameters) - # email.set_some_obscure_header "frobnicate" - # MyMailer.deliver(email) - def deliver(mail) - new.deliver!(mail) + # TODO The delivery should happen inside the instrument block + def delivered_email(mail) + ActiveSupport::Notifications.instrument("action_mailer.deliver") do |payload| + self.set_payload_for_mail(payload, mail) + end end - def template_root - self.view_paths && self.view_paths.first + def respond_to?(method, *args) #:nodoc: + super || action_methods.include?(method.to_s) end - # Should template root overwrite the whole view_paths? - def template_root=(root) - self.view_paths = ActionView::Base.process_view_paths(root) - end + protected def set_payload_for_mail(payload, mail) #:nodoc: + payload[:mailer] = self.name payload[:message_id] = mail.message_id payload[:subject] = mail.subject payload[:to] = mail.to @@ -436,61 +339,16 @@ module ActionMailer #:nodoc: payload[:mail] = mail.encoded end - private - - def matches_dynamic_method?(method_name) #:nodoc: - method_name = method_name.to_s - /^(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name) + def method_missing(method, *args) #:nodoc: + if action_methods.include?(method.to_s) + new(method, *args).message + else + super end - end - - # Configure delivery method. Check ActionMailer::DeliveryMethod for more - # instructions. - superclass_delegating_reader :delivery_method - self.delivery_method = :smtp - - # 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) - ActiveSupport::Deprecation.warn('Passing custom headers with :headers => {} is deprecated. ' << - 'Please just pass in custom headers directly.', caller[0,10]) - 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) - super # Run deprecation hooks - - params = { :content_type => params } if String === params - params = { :content_disposition => "attachment", - :content_transfer_encoding => "base64" }.merge(params) - - part(params, &block) end - # Allow you to set assigns for your template: - # - # body :greetings => "Hi" - # - # Will make @greetings available in the template to be rendered. - def body(object=nil) - returning(super) do # Run deprecation hooks - if object.is_a?(Hash) - @assigns_set = true - object.each { |k, v| instance_variable_set(:"@#{k}", v) } - end - end - end + attr_internal :message # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer # will be initialized according to the named method. If not, the mailer will @@ -498,167 +356,133 @@ module ActionMailer #:nodoc: # method, for instance). def initialize(method_name=nil, *args) super() + @_message = Mail.new process(method_name, *args) if method_name end - # Process the mailer via the given +method_name+. The body will be - # rendered and a new Mail object created. - def process(method_name, *args) - initialize_defaults(method_name) - super - - # Create e-mail parts - create_parts - - # Set the subject if not set yet - @subject ||= I18n.t(:subject, :scope => [:actionmailer, mailer_name, method_name], - :default => method_name.humanize) - - # Build the mail object itself - create_mail + def headers(args=nil) + if args + ActiveSupport::Deprecation.warn "headers(Hash) is deprecated, please do headers[key] = value instead", caller[0,2] + @headers = args + else + @_message + end end - # Delivers a Mail object. By default, it delivers the cached mail - # object (from the <tt>create!</tt> method). If no cached mail object exists, and - # no alternate has been given as the parameter, this will fail. - def deliver!(mail = @mail) - raise "no mail object available for delivery!" unless mail - - ActiveSupport::Notifications.instrument("action_mailer.deliver", - :template => template, :mailer => self.class.name) do |payload| - - self.class.set_payload_for_mail(payload, mail) + def attachments + @_message.attachments + end - begin - self.delivery_method.perform_delivery(mail) if perform_deliveries - rescue Exception => e # Net::SMTP errors or sendmail pipe errors - raise e if raise_delivery_errors - end + def mail(headers={}, &block) + # Guard flag to prevent both the old and the new API from firing + # Should be removed when old API is removed + @mail_was_called = true + m = @_message + + # Give preference to headers and fallback to the ones set in mail + content_type = headers[:content_type] || m.content_type + charset = headers[:charset] || m.charset || self.class.default_charset.dup + mime_version = headers[:mime_version] || m.mime_version || self.class.default_mime_version.dup + + # Set subjects and fields quotings + headers[:subject] ||= default_subject + quote_fields!(headers, charset) + + # Render the templates and blocks + responses, sort_order = collect_responses_and_sort_order(headers, &block) + content_type ||= create_parts_from_responses(m, responses, charset) + + # Tidy up content type, charset, mime version and sort order + m.content_type = content_type + m.charset = charset + m.mime_version = mime_version + sort_order = headers[:parts_order] || sort_order || self.class.default_implicit_parts_order.dup + + if m.multipart? + m.body.set_sort_order(sort_order) + m.body.sort_parts! end - mail + # Finaly set delivery behavior configured in class + wrap_delivery_behavior!(headers[:delivery_method]) + m end - private + protected - # Render a message but does not set it as mail body. Useful for rendering - # data for part and attachments. - # - # Examples: - # - # render_message "special_message" - # render_message :template => "special_message" - # render_message :inline => "<%= 'Hi!' %>" - def render_message(object) - case object - when String - render_to_body(:template => object) - else - render_to_body(object) - end - 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) #:nodoc: - @charset ||= @@default_charset.dup - @content_type ||= @@default_content_type.dup - @implicit_parts_order ||= @@default_implicit_parts_order.dup - @mime_version ||= @@default_mime_version.dup if @@default_mime_version - - @mailer_name ||= self.class.mailer_name.dup - @template ||= method_name + def default_subject #:nodoc: + mailer_scope = self.class.mailer_name.gsub('/', '.') + I18n.t(:subject, :scope => [:actionmailer, mailer_scope, action_name], :default => action_name.humanize) + end - @parts ||= [] - @headers ||= {} - @sent_on ||= Time.now + # TODO: Move this into Mail + def quote_fields!(headers, charset) #:nodoc: + m = @_message + m.subject ||= quote_if_necessary(headers[:subject], charset) if headers[:subject] + m.to ||= quote_address_if_necessary(headers[:to], charset) if headers[:to] + m.from ||= quote_address_if_necessary(headers[:from], charset) if headers[:from] + m.cc ||= quote_address_if_necessary(headers[:cc], charset) if headers[:cc] + m.bcc ||= quote_address_if_necessary(headers[:bcc], charset) if headers[:bcc] + m.reply_to ||= quote_address_if_necessary(headers[:reply_to], charset) if headers[:reply_to] + m.date ||= headers[:date] if headers[:date] + end - super # Run deprecation hooks + def collect_responses_and_sort_order(headers) #:nodoc: + responses, sort_order = [], nil + + if block_given? + collector = ActionMailer::Collector.new(self) { render(action_name) } + yield(collector) + sort_order = collector.responses.map { |r| r[:content_type] } + responses = collector.responses + elsif headers[:body] + responses << { + :body => headers[:body], + :content_type => self.class.default_content_type.dup + } + else + each_template do |template| + responses << { + :body => render_to_body(:_template => template), + :content_type => template.mime_type.to_s + } + end end - def create_parts #:nodoc: - super # Run deprecation hooks - - if String === response_body - @parts.unshift create_inline_part(response_body) - else - self.class.template_root.find_all(@template, {}, @mailer_name).each do |template| - @parts << create_inline_part(render_to_body(:_template => template), template.mime_type) - end - - if @parts.size > 1 - @content_type = "multipart/alternative" if @content_type !~ /^multipart/ - end + [responses, sort_order] + 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? + def each_template(&block) #:nodoc: + self.class.view_paths.each do |load_paths| + templates = load_paths.find_all(action_name, {}, self.class.mailer_name) + unless templates.empty? + templates.each(&block) + return end end + end - def create_inline_part(body, mime_type=nil) #:nodoc: - 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 - ) + def create_parts_from_responses(m, responses, charset) #:nodoc: + if responses.size == 1 && !m.has_attachments? + m.body = responses[0][:body] + return responses[0][:content_type] + elsif responses.size > 1 && m.has_attachments? + container = Mail::Part.new + container.content_type = "multipart/alternate" + responses.each { |r| insert_part(container, r, charset) } + m.add_part(container) + else + responses.each { |r| insert_part(m, r, charset) } end - def create_mail #:nodoc: - m = Mail.new - - m.subject, = quote_any_if_necessary(charset, subject) - m.to, m.from = quote_any_address_if_necessary(charset, recipients, from) - m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil? - m.cc = quote_address_if_necessary(cc, charset) unless cc.nil? - m.reply_to = quote_address_if_necessary(reply_to, charset) unless reply_to.nil? - 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 - - m.content_transfer_encoding = '8bit' unless m.body.only_us_ascii? - - @mail = m - end - - def split_content_type(ct) #:nodoc: - ct.to_s.split("/") - end + m.has_attachments? ? "multipart/mixed" : "multipart/alternate" + end - def parse_content_type(defaults=nil) #:nodoc: - 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 + def insert_part(container, response, charset) #:nodoc: + response[:charset] ||= charset + part = Mail::Part.new(response) + container.add_part(part) + end end end diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb new file mode 100644 index 0000000000..5431efccfe --- /dev/null +++ b/actionmailer/lib/action_mailer/collector.rb @@ -0,0 +1,36 @@ +require 'abstract_controller/collector' +require 'active_support/core_ext/hash/reverse_merge' +require 'active_support/core_ext/array/extract_options' + +module ActionMailer #:nodoc: + class Collector + include AbstractController::Collector + attr_reader :responses + + def initialize(context, &block) + @context = context + @responses = [] + @default_render = block + @default_formats = context.formats + end + + def any(*args, &block) + options = args.extract_options! + raise "You have to supply at least one format" if args.empty? + args.each { |type| send(type, options.dup, &block) } + end + alias :all :any + + def custom(mime, options={}, &block) + options.reverse_merge!(:content_type => mime.to_s) + @context.formats = [mime.to_sym] + options[:body] = if block + block.call + else + @default_render.call + end + @responses << options + @context.formats = @default_formats + end + end +end
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/delivery_method.rb b/actionmailer/lib/action_mailer/delivery_method.rb deleted file mode 100644 index 4f7d3afc3c..0000000000 --- a/actionmailer/lib/action_mailer/delivery_method.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'active_support/core_ext/class' - -module ActionMailer - module DeliveryMethod - autoload :File, 'action_mailer/delivery_method/file' - autoload :Sendmail, 'action_mailer/delivery_method/sendmail' - autoload :Smtp, 'action_mailer/delivery_method/smtp' - autoload :Test, 'action_mailer/delivery_method/test' - - # Creates a new DeliveryMethod object according to the given options. - # - # If no arguments are passed to this method, then a new - # ActionMailer::DeliveryMethod::Stmp object will be returned. - # - # If you pass a Symbol as the first argument, then a corresponding - # delivery method class under the ActionMailer::DeliveryMethod namespace - # will be created. - # For example: - # - # ActionMailer::DeliveryMethod.lookup_method(:sendmail) - # # => returns a new ActionMailer::DeliveryMethod::Sendmail object - # - # If the first argument is not a Symbol, then it will simply be returned: - # - # ActionMailer::DeliveryMethod.lookup_method(MyOwnDeliveryMethod.new) - # # => returns MyOwnDeliveryMethod.new - def self.lookup_method(delivery_method) - case delivery_method - when Symbol - method_name = delivery_method.to_s.camelize - method_class = ActionMailer::DeliveryMethod.const_get(method_name) - method_class.new - when nil # default - Smtp.new - else - delivery_method - end - end - - # An abstract delivery method class. There are multiple delivery method classes. - # See the classes under the ActionMailer::DeliveryMethod, e.g. - # ActionMailer::DeliveryMethod::Smtp. - # Smtp is the default delivery method for production - # while Test is used in testing. - # - # each delivery method exposes just one method - # - # delivery_method = ActionMailer::DeliveryMethod::Smtp.new - # delivery_method.perform_delivery(mail) # send the mail via smtp - # - class Method - superclass_delegating_accessor :settings - self.settings = {} - end - end -end diff --git a/actionmailer/lib/action_mailer/delivery_method/file.rb b/actionmailer/lib/action_mailer/delivery_method/file.rb deleted file mode 100644 index 571e32df49..0000000000 --- a/actionmailer/lib/action_mailer/delivery_method/file.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'tmpdir' - -module ActionMailer - module DeliveryMethod - - # A delivery method implementation which writes all mails to a file. - class File < Method - self.settings = { - :location => defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" - } - - def perform_delivery(mail) - FileUtils.mkdir_p settings[:location] - - mail.destinations.uniq.each do |to| - ::File.open(::File.join(settings[:location], to), 'a') { |f| f.write(mail) } - end - end - end - end -end diff --git a/actionmailer/lib/action_mailer/delivery_method/sendmail.rb b/actionmailer/lib/action_mailer/delivery_method/sendmail.rb deleted file mode 100644 index db55af79f1..0000000000 --- a/actionmailer/lib/action_mailer/delivery_method/sendmail.rb +++ /dev/null @@ -1,22 +0,0 @@ -module ActionMailer - module DeliveryMethod - - # A delivery method implementation which sends via sendmail. - class Sendmail < Method - self.settings = { - :location => '/usr/sbin/sendmail', - :arguments => '-i -t' - } - - def perform_delivery(mail) - sendmail_args = settings[:arguments] - sendmail_args += " -f \"#{mail['return-path']}\"" if mail['return-path'] - IO.popen("#{settings[:location]} #{sendmail_args}","w+") do |sm| - sm.print(mail.encoded.gsub(/\r/, '')) - sm.flush - end - end - end - - end -end diff --git a/actionmailer/lib/action_mailer/delivery_method/smtp.rb b/actionmailer/lib/action_mailer/delivery_method/smtp.rb deleted file mode 100644 index af30c498b5..0000000000 --- a/actionmailer/lib/action_mailer/delivery_method/smtp.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'net/smtp' - -module ActionMailer - module DeliveryMethod - # A delivery method implementation which sends via smtp. - class Smtp < Method - self.settings = { - :address => "localhost", - :port => 25, - :domain => 'localhost.localdomain', - :user_name => nil, - :password => nil, - :authentication => nil, - :enable_starttls_auto => true, - } - - def perform_delivery(mail) - destinations = mail.destinations - sender = (mail['return-path'] && mail['return-path'].address) || mail['from'] - - smtp = Net::SMTP.new(settings[:address], settings[:port]) - smtp.enable_starttls_auto if settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto) - smtp.start(settings[:domain], settings[:user_name], settings[:password], - settings[:authentication]) do |smtp| - smtp.sendmail(mail.encoded, sender, destinations) - end - end - end - end -end diff --git a/actionmailer/lib/action_mailer/delivery_method/test.rb b/actionmailer/lib/action_mailer/delivery_method/test.rb deleted file mode 100644 index 6e3239d52a..0000000000 --- a/actionmailer/lib/action_mailer/delivery_method/test.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ActionMailer - module DeliveryMethod - - # A delivery method implementation designed for testing, which just appends each record to the :deliveries array - class Test < Method - def perform_delivery(mail) - ActionMailer::Base.deliveries << mail - end - end - - end -end diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb new file mode 100644 index 0000000000..21909d5d57 --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -0,0 +1,87 @@ +require 'tmpdir' + +module ActionMailer + # This modules handles everything related to the delivery, from registering new + # delivery methods to configuring the mail object to be send. + module DeliveryMethods + extend ActiveSupport::Concern + + included do + extlib_inheritable_accessor :delivery_methods, :delivery_method, + :instance_writer => false + + # Do not make this inheritable, because we always want it to propagate + cattr_accessor :raise_delivery_errors + self.raise_delivery_errors = true + + cattr_accessor :perform_deliveries + self.perform_deliveries = true + + self.delivery_methods = {} + self.delivery_method = :smtp + + add_delivery_method :smtp, Mail::SMTP, + :address => "localhost", + :port => 25, + :domain => 'localhost.localdomain', + :user_name => nil, + :password => nil, + :authentication => nil, + :enable_starttls_auto => true + + add_delivery_method :file, Mail::FileDelivery, + :location => defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" + + add_delivery_method :sendmail, Mail::Sendmail, + :location => '/usr/sbin/sendmail', + :arguments => '-i -t' + + add_delivery_method :test, Mail::TestMailer + end + + module ClassMethods + # Provides a list of emails that have been delivered by Mail + delegate :deliveries, :deliveries=, :to => Mail + + # Adds a new delivery method through the given class using the given symbol + # as alias and the default options supplied: + # + # Example: + # + # add_delivery_method :sendmail, Mail::Sendmail, + # :location => '/usr/sbin/sendmail', + # :arguments => '-i -t' + # + def add_delivery_method(symbol, klass, default_options={}) + unless respond_to?(:"#{symbol}_settings") + extlib_inheritable_accessor(:"#{symbol}_settings", :instance_writer => false) + end + + send(:"#{symbol}_settings=", default_options) + self.delivery_methods[symbol.to_sym] = klass + end + + def wrap_delivery_behavior(mail, method=nil) #:nodoc: + method ||= self.delivery_method + mail.register_for_delivery_notification(self) + + if method.is_a?(Symbol) + if klass = delivery_methods[method.to_sym] + mail.delivery_method(klass, send(:"#{method}_settings")) + else + raise "Invalid delivery method #{method.inspect}" + end + else + mail.delivery_method(method) + end + + mail.perform_deliveries = perform_deliveries + mail.raise_delivery_errors = raise_delivery_errors + end + end + + def wrap_delivery_behavior!(*args) #:nodoc: + self.class.wrap_delivery_behavior(message, *args) + end + end +end
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/deprecated_api.rb b/actionmailer/lib/action_mailer/deprecated_api.rb new file mode 100644 index 0000000000..0eb8d85676 --- /dev/null +++ b/actionmailer/lib/action_mailer/deprecated_api.rb @@ -0,0 +1,112 @@ +module ActionMailer + # This is the API which is deprecated and is going to be removed on Rails 3.1 release. + # Part of the old API will be deprecated after 3.1, for a smoother deprecation process. + # Chech those in OldApi instead. + module DeprecatedApi #:nodoc: + extend ActiveSupport::Concern + + module ClassMethods + + # Deliver the given mail object directly. This can be used to deliver + # a preconstructed mail object, like: + # + # email = MyMailer.create_some_mail(parameters) + # email.set_some_obscure_header "frobnicate" + # MyMailer.deliver(email) + def deliver(mail, show_warning=true) + if show_warning + ActiveSupport::Deprecation.warn "#{self}.deliver is deprecated, call " << + "deliver in the mailer instance instead", caller[0,2] + end + + raise "no mail object available for delivery!" unless mail + wrap_delivery_behavior(mail) + mail.deliver + mail + end + + def template_root + self.view_paths && self.view_paths.first + end + + def template_root=(root) + ActiveSupport::Deprecation.warn "template_root= is deprecated, use view_paths.unshift instead", caller[0,2] + self.view_paths = ActionView::Base.process_view_paths(root) + end + + def respond_to?(method_symbol, include_private = false) + matches_dynamic_method?(method_symbol) || super + end + + def method_missing(method_symbol, *parameters) + if match = matches_dynamic_method?(method_symbol) + case match[1] + when 'create' + ActiveSupport::Deprecation.warn "#{self}.create_#{match[2]} is deprecated, " << + "use #{self}.#{match[2]} instead", caller[0,2] + new(match[2], *parameters).message + when 'deliver' + ActiveSupport::Deprecation.warn "#{self}.deliver_#{match[2]} is deprecated, " << + "use #{self}.#{match[2]}.deliver instead", caller[0,2] + new(match[2], *parameters).message.deliver + else super + end + else + super + end + end + + private + + def matches_dynamic_method?(method_name) + method_name = method_name.to_s + /^(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name) + end + end + + # Delivers a Mail object. By default, it delivers the cached mail + # object (from the <tt>create!</tt> method). If no cached mail object exists, and + # no alternate has been given as the parameter, this will fail. + def deliver!(mail = @_message) + ActiveSupport::Deprecation.warn "Calling deliver in the AM::Base object is deprecated, " << + "please call deliver in the Mail instance", caller[0,2] + self.class.deliver(mail, false) + end + alias :deliver :deliver! + + def render(*args) + options = args.last.is_a?(Hash) ? args.last : {} + if options[:body] + ActiveSupport::Deprecation.warn(':body in render deprecated. Please use instance ' << + 'variables as assigns instead', caller[0,1]) + body options.delete(:body) + end + super + end + + # Render a message but does not set it as mail body. Useful for rendering + # data for part and attachments. + # + # Examples: + # + # render_message "special_message" + # render_message :template => "special_message" + # render_message :inline => "<%= 'Hi!' %>" + # + def render_message(*args) + ActiveSupport::Deprecation.warn "render_message is deprecated, use render instead", caller[0,2] + render(*args) + end + + private + + def create_parts + if @body.is_a?(Hash) && !@body.empty? + ActiveSupport::Deprecation.warn "Giving a hash to body is deprecated, please use instance variables instead", caller[0,2] + @body.each { |k, v| instance_variable_set(:"@#{k}", v) } + end + super + end + + end +end
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/deprecated_body.rb b/actionmailer/lib/action_mailer/deprecated_body.rb deleted file mode 100644 index 5379b33a54..0000000000 --- a/actionmailer/lib/action_mailer/deprecated_body.rb +++ /dev/null @@ -1,46 +0,0 @@ -module ActionMailer - # TODO Remove this module all together in a next release. Ensure that super - # hooks and @assigns_set in ActionMailer::Base are removed as well. - module DeprecatedBody - extend ActionMailer::AdvAttrAccessor - - # 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 - - def initialize_defaults(method_name) - @body ||= {} - end - - def attachment(params, &block) - if params[:body] - ActiveSupport::Deprecation.warn('attachment :body => "string" is deprecated. To set the body of an attachment ' << - 'please use :data instead, like attachment :data => "string"', caller[0,10]) - params[:data] = params.delete(:body) - end - end - - def create_parts - if String === @body && !defined?(@assigns_set) - ActiveSupport::Deprecation.warn('body(String) is deprecated. To set the body with a text ' << - 'call render(:text => "body")', caller[0,10]) - self.response_body = @body - elsif self.response_body - @body = self.response_body - end - end - - def render(*args) - options = args.last.is_a?(Hash) ? args.last : {} - if options[:body] - ActiveSupport::Deprecation.warn(':body in render deprecated. Please call body ' << - 'with a hash instead', caller[0,1]) - - body options.delete(:body) - end - - super - end - end -end diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb index df71330fd5..ab5c3469b2 100644 --- a/actionmailer/lib/action_mailer/mail_helper.rb +++ b/actionmailer/lib/action_mailer/mail_helper.rb @@ -17,8 +17,13 @@ module ActionMailer end # Access the mailer instance. - def mailer #:nodoc: + def mailer @_controller end + + # Access the message instance. + def message + @_message + end end end diff --git a/actionmailer/lib/action_mailer/old_api.rb b/actionmailer/lib/action_mailer/old_api.rb new file mode 100644 index 0000000000..f5b077ab98 --- /dev/null +++ b/actionmailer/lib/action_mailer/old_api.rb @@ -0,0 +1,250 @@ +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 additional headers to be added to the message. + adv_attr_accessor :headers + + # 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 + + quote_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.dup + @content_type ||= self.class.default_content_type.dup + @implicit_parts_order ||= self.class.default_implicit_parts_order.dup + @mime_version ||= self.class.default_mime_version.dup if self.class.default_mime_version + + @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 + self.response_body = @body + end + + if String === response_body + @parts.unshift create_inline_part(response_body) + else + self.class.view_paths.first.find_all(@template, {}, @mailer_name).each do |template| + @parts << create_inline_part(render_to_body(:_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 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
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb index 0ca4f5494e..7c4033a125 100644 --- a/actionmailer/lib/action_mailer/test_case.rb +++ b/actionmailer/lib/action_mailer/test_case.rb @@ -37,7 +37,7 @@ module ActionMailer def initialize_test_deliveries ActionMailer::Base.delivery_method = :test ActionMailer::Base.perform_deliveries = true - ActionMailer::Base.deliveries = [] + ActionMailer::Base.deliveries.clear end def set_expected_mail diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index f234c0248c..3a1612442f 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -58,7 +58,6 @@ module ActionMailer end end -# TODO: Deprecate this module Test module Unit class TestCase diff --git a/actionmailer/lib/action_mailer/tmail_compat.rb b/actionmailer/lib/action_mailer/tmail_compat.rb index 2fd25ff145..c6efdc53b6 100644 --- a/actionmailer/lib/action_mailer/tmail_compat.rb +++ b/actionmailer/lib/action_mailer/tmail_compat.rb @@ -2,19 +2,27 @@ module Mail class Message def set_content_type(*args) - STDERR.puts("Message#set_content_type is deprecated, please just call Message#content_type with the same arguments.\n#{caller}") + ActiveSupport::Deprecation.warn('Message#set_content_type is deprecated, please just call ' << + 'Message#content_type with the same arguments', caller[0,2]) content_type(*args) end alias :old_transfer_encoding :transfer_encoding def transfer_encoding(value = nil) if value - STDERR.puts("Message#transfer_encoding is deprecated, please call Message#content_transfer_encoding with the same arguments.\n#{caller}") + ActiveSupport::Deprecation.warn('Message#transfer_encoding is deprecated, please call ' << + 'Message#content_transfer_encoding with the same arguments', caller[0,2]) content_transfer_encoding(value) else old_transfer_encoding end end + def original_filename + ActiveSupport::Deprecation.warn('Message#original_filename is deprecated, ' << + 'please call Message#filename', caller[0,2]) + filename + end + end end
\ No newline at end of file |