actionmailer 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "abstract_controller/collector"
4
+ require "active_support/core_ext/hash/reverse_merge"
5
+ require "active_support/core_ext/array/extract_options"
6
+
7
+ module ActionMailer
8
+ class Collector
9
+ include AbstractController::Collector
10
+ attr_reader :responses
11
+
12
+ def initialize(context, &block)
13
+ @context = context
14
+ @responses = []
15
+ @default_render = block
16
+ end
17
+
18
+ def any(*args, &block)
19
+ options = args.extract_options!
20
+ raise ArgumentError, "You have to supply at least one format" if args.empty?
21
+ args.each { |type| send(type, options.dup, &block) }
22
+ end
23
+ alias :all :any
24
+
25
+ def custom(mime, options = {})
26
+ options.reverse_merge!(content_type: mime.to_s)
27
+ @context.formats = [mime.to_sym]
28
+ options[:body] = block_given? ? yield : @default_render.call
29
+ @responses << options
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job"
4
+
5
+ module ActionMailer
6
+ # The <tt>ActionMailer::DeliveryJob</tt> class is used when you
7
+ # want to send emails outside of the request-response cycle.
8
+ #
9
+ # Exceptions are rescued and handled by the mailer class.
10
+ class DeliveryJob < ActiveJob::Base # :nodoc:
11
+ queue_as { ActionMailer::Base.deliver_later_queue_name }
12
+
13
+ rescue_from StandardError, with: :handle_exception_with_mailer_class
14
+
15
+ before_perform do
16
+ ActiveSupport::Deprecation.warn <<~MSG.squish
17
+ Sending mail with DeliveryJob and Parameterized::DeliveryJob
18
+ is deprecated and will be removed in Rails 6.1.
19
+ Please use MailDeliveryJob instead.
20
+ MSG
21
+ end
22
+
23
+ def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
24
+ mailer.constantize.public_send(mail_method, *args).send(delivery_method)
25
+ end
26
+
27
+ private
28
+ # "Deserialize" the mailer class name by hand in case another argument
29
+ # (like a Global ID reference) raised DeserializationError.
30
+ def mailer_class
31
+ if mailer = Array(@serialized_arguments).first || Array(arguments).first
32
+ mailer.constantize
33
+ end
34
+ end
35
+
36
+ def handle_exception_with_mailer_class(exception)
37
+ if klass = mailer_class
38
+ klass.handle_exception exception
39
+ else
40
+ raise exception
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+
5
+ module ActionMailer
6
+ # This module handles everything related to mail delivery, from registering
7
+ # new delivery methods to configuring the mail object to be sent.
8
+ module DeliveryMethods
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ # Do not make this inheritable, because we always want it to propagate
13
+ cattr_accessor :raise_delivery_errors, default: true
14
+ cattr_accessor :perform_deliveries, default: true
15
+ cattr_accessor :deliver_later_queue_name, default: :mailers
16
+
17
+ class_attribute :delivery_methods, default: {}.freeze
18
+ class_attribute :delivery_method, default: :smtp
19
+
20
+ add_delivery_method :smtp, Mail::SMTP,
21
+ address: "localhost",
22
+ port: 25,
23
+ domain: "localhost.localdomain",
24
+ user_name: nil,
25
+ password: nil,
26
+ authentication: nil,
27
+ enable_starttls_auto: true
28
+
29
+ add_delivery_method :file, Mail::FileDelivery,
30
+ location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
31
+
32
+ add_delivery_method :sendmail, Mail::Sendmail,
33
+ location: "/usr/sbin/sendmail",
34
+ arguments: "-i"
35
+
36
+ add_delivery_method :test, Mail::TestMailer
37
+ end
38
+
39
+ # Helpers for creating and wrapping delivery behavior, used by DeliveryMethods.
40
+ module ClassMethods
41
+ # Provides a list of emails that have been delivered by Mail::TestMailer
42
+ delegate :deliveries, :deliveries=, to: Mail::TestMailer
43
+
44
+ # Adds a new delivery method through the given class using the given
45
+ # symbol as alias and the default options supplied.
46
+ #
47
+ # add_delivery_method :sendmail, Mail::Sendmail,
48
+ # location: '/usr/sbin/sendmail',
49
+ # arguments: '-i'
50
+ def add_delivery_method(symbol, klass, default_options = {})
51
+ class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
52
+ send(:"#{symbol}_settings=", default_options)
53
+ self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
54
+ end
55
+
56
+ def wrap_delivery_behavior(mail, method = nil, options = nil) # :nodoc:
57
+ method ||= delivery_method
58
+ mail.delivery_handler = self
59
+
60
+ case method
61
+ when NilClass
62
+ raise "Delivery method cannot be nil"
63
+ when Symbol
64
+ if klass = delivery_methods[method]
65
+ mail.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {}))
66
+ else
67
+ raise "Invalid delivery method #{method.inspect}"
68
+ end
69
+ else
70
+ mail.delivery_method(method)
71
+ end
72
+
73
+ mail.perform_deliveries = perform_deliveries
74
+ mail.raise_delivery_errors = raise_delivery_errors
75
+ end
76
+ end
77
+
78
+ def wrap_delivery_behavior!(*args) # :nodoc:
79
+ self.class.wrap_delivery_behavior(message, *args)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMailer
4
+ # Returns the version of the currently loaded Action Mailer as a <tt>Gem::Version</tt>.
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 6
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module ActionMailer
6
+ # Implements a mailer preview interceptor that converts image tag src attributes
7
+ # that use inline cid: style URLs to data: style URLs so that they are visible
8
+ # when previewing an HTML email in a web browser.
9
+ #
10
+ # This interceptor is enabled by default. To disable it, delete it from the
11
+ # <tt>ActionMailer::Base.preview_interceptors</tt> array:
12
+ #
13
+ # ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
14
+ #
15
+ class InlinePreviewInterceptor
16
+ PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
17
+
18
+ include Base64
19
+
20
+ def self.previewing_email(message) #:nodoc:
21
+ new(message).transform!
22
+ end
23
+
24
+ def initialize(message) #:nodoc:
25
+ @message = message
26
+ end
27
+
28
+ def transform! #:nodoc:
29
+ return message if html_part.blank?
30
+
31
+ html_part.body = html_part.decoded.gsub(PATTERN) do |match|
32
+ if part = find_part(match[9..-2])
33
+ %[src="#{data_url(part)}"]
34
+ else
35
+ match
36
+ end
37
+ end
38
+
39
+ message
40
+ end
41
+
42
+ private
43
+ attr_reader :message
44
+
45
+ def html_part
46
+ @html_part ||= message.html_part
47
+ end
48
+
49
+ def data_url(part)
50
+ "data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
51
+ end
52
+
53
+ def find_part(cid)
54
+ message.all_parts.find { |p| p.attachment? && p.cid == cid }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/log_subscriber"
4
+
5
+ module ActionMailer
6
+ # Implements the ActiveSupport::LogSubscriber for logging notifications when
7
+ # email is delivered or received.
8
+ class LogSubscriber < ActiveSupport::LogSubscriber
9
+ # An email was delivered.
10
+ def deliver(event)
11
+ info do
12
+ perform_deliveries = event.payload[:perform_deliveries]
13
+ if perform_deliveries
14
+ "Delivered mail #{event.payload[:message_id]} (#{event.duration.round(1)}ms)"
15
+ else
16
+ "Skipped delivery of mail #{event.payload[:message_id]} as `perform_deliveries` is false"
17
+ end
18
+ end
19
+
20
+ debug { event.payload[:mail] }
21
+ end
22
+
23
+ # An email was received.
24
+ def receive(event)
25
+ info { "Received mail (#{event.duration.round(1)}ms)" }
26
+ debug { event.payload[:mail] }
27
+ end
28
+
29
+ # An email was generated.
30
+ def process(event)
31
+ debug do
32
+ mailer = event.payload[:mailer]
33
+ action = event.payload[:action]
34
+ "#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms"
35
+ end
36
+ end
37
+
38
+ # Use the logger configured for ActionMailer::Base.
39
+ def logger
40
+ ActionMailer::Base.logger
41
+ end
42
+ end
43
+ end
44
+
45
+ ActionMailer::LogSubscriber.attach_to :action_mailer
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job"
4
+
5
+ module ActionMailer
6
+ # The <tt>ActionMailer::MailDeliveryJob</tt> class is used when you
7
+ # want to send emails outside of the request-response cycle. It supports
8
+ # sending either parameterized or normal mail.
9
+ #
10
+ # Exceptions are rescued and handled by the mailer class.
11
+ class MailDeliveryJob < ActiveJob::Base # :nodoc:
12
+ queue_as { ActionMailer::Base.deliver_later_queue_name }
13
+
14
+ rescue_from StandardError, with: :handle_exception_with_mailer_class
15
+
16
+ def perform(mailer, mail_method, delivery_method, args:, params: nil) #:nodoc:
17
+ mailer_class = params ? mailer.constantize.with(params) : mailer.constantize
18
+ mailer_class.public_send(mail_method, *args).send(delivery_method)
19
+ end
20
+
21
+ private
22
+ # "Deserialize" the mailer class name by hand in case another argument
23
+ # (like a Global ID reference) raised DeserializationError.
24
+ def mailer_class
25
+ if mailer = Array(@serialized_arguments).first || Array(arguments).first
26
+ mailer.constantize
27
+ end
28
+ end
29
+
30
+ def handle_exception_with_mailer_class(exception)
31
+ if klass = mailer_class
32
+ klass.handle_exception exception
33
+ else
34
+ raise exception
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMailer
4
+ # Provides helper methods for ActionMailer::Base that can be used for easily
5
+ # formatting messages, accessing mailer or message instances, and the
6
+ # attachments list.
7
+ module MailHelper
8
+ # Take the text and format it, indented two spaces for each line, and
9
+ # wrapped at 72 columns:
10
+ #
11
+ # text = <<-TEXT
12
+ # This is
13
+ # the paragraph.
14
+ #
15
+ # * item1 * item2
16
+ # TEXT
17
+ #
18
+ # block_format text
19
+ # # => " This is the paragraph.\n\n * item1\n * item2\n"
20
+ def block_format(text)
21
+ formatted = text.split(/\n\r?\n/).collect { |paragraph|
22
+ format_paragraph(paragraph)
23
+ }.join("\n\n")
24
+
25
+ # Make list points stand on their own line
26
+ formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { " #{$1} #{$2.strip}\n" }
27
+ formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { " #{$1} #{$2.strip}\n" }
28
+
29
+ formatted
30
+ end
31
+
32
+ # Access the mailer instance.
33
+ def mailer
34
+ @_controller
35
+ end
36
+
37
+ # Access the message instance.
38
+ def message
39
+ @_message
40
+ end
41
+
42
+ # Access the message attachments list.
43
+ def attachments
44
+ mailer.attachments
45
+ end
46
+
47
+ # Returns +text+ wrapped at +len+ columns and indented +indent+ spaces.
48
+ # By default column length +len+ equals 72 characters and indent
49
+ # +indent+ equal two spaces.
50
+ #
51
+ # my_text = 'Here is a sample text with more than 40 characters'
52
+ #
53
+ # format_paragraph(my_text, 25, 4)
54
+ # # => " Here is a sample text with\n more than 40 characters"
55
+ def format_paragraph(text, len = 72, indent = 2)
56
+ sentences = [[]]
57
+
58
+ text.split.each do |word|
59
+ if sentences.first.present? && (sentences.last + [word]).join(" ").length > len
60
+ sentences << [word]
61
+ else
62
+ sentences.last << word
63
+ end
64
+ end
65
+
66
+ indentation = " " * indent
67
+ sentences.map! { |sentence|
68
+ "#{indentation}#{sentence.join(' ')}"
69
+ }.join "\n"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module ActionMailer
6
+ # The <tt>ActionMailer::MessageDelivery</tt> class is used by
7
+ # ActionMailer::Base when creating a new mailer.
8
+ # <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
9
+ # created <tt>Mail::Message</tt>. You can get direct access to the
10
+ # <tt>Mail::Message</tt>, deliver the email or schedule the email to be sent
11
+ # through Active Job.
12
+ #
13
+ # Notifier.welcome(User.first) # an ActionMailer::MessageDelivery object
14
+ # Notifier.welcome(User.first).deliver_now # sends the email
15
+ # Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
16
+ # Notifier.welcome(User.first).message # a Mail::Message object
17
+ class MessageDelivery < Delegator
18
+ def initialize(mailer_class, action, *args) #:nodoc:
19
+ @mailer_class, @action, @args = mailer_class, action, args
20
+
21
+ # The mail is only processed if we try to call any methods on it.
22
+ # Typical usage will leave it unloaded and call deliver_later.
23
+ @processed_mailer = nil
24
+ @mail_message = nil
25
+ end
26
+
27
+ # Method calls are delegated to the Mail::Message that's ready to deliver.
28
+ def __getobj__ #:nodoc:
29
+ @mail_message ||= processed_mailer.message
30
+ end
31
+
32
+ # Unused except for delegator internals (dup, marshalling).
33
+ def __setobj__(mail_message) #:nodoc:
34
+ @mail_message = mail_message
35
+ end
36
+
37
+ # Returns the resulting Mail::Message
38
+ def message
39
+ __getobj__
40
+ end
41
+
42
+ # Was the delegate loaded, causing the mailer action to be processed?
43
+ def processed?
44
+ @processed_mailer || @mail_message
45
+ end
46
+
47
+ # Enqueues the email to be delivered through Active Job. When the
48
+ # job runs it will send the email using +deliver_now!+. That means
49
+ # that the message will be sent bypassing checking +perform_deliveries+
50
+ # and +raise_delivery_errors+, so use with caution.
51
+ #
52
+ # Notifier.welcome(User.first).deliver_later!
53
+ # Notifier.welcome(User.first).deliver_later!(wait: 1.hour)
54
+ # Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now)
55
+ #
56
+ # Options:
57
+ #
58
+ # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
59
+ # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
60
+ # * <tt>:queue</tt> - Enqueue the email on the specified queue
61
+ #
62
+ # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
63
+ # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
64
+ # +delivery_job+.
65
+ #
66
+ # class AccountRegistrationMailer < ApplicationMailer
67
+ # self.delivery_job = RegistrationDeliveryJob
68
+ # end
69
+ def deliver_later!(options = {})
70
+ enqueue_delivery :deliver_now!, options
71
+ end
72
+
73
+ # Enqueues the email to be delivered through Active Job. When the
74
+ # job runs it will send the email using +deliver_now+.
75
+ #
76
+ # Notifier.welcome(User.first).deliver_later
77
+ # Notifier.welcome(User.first).deliver_later(wait: 1.hour)
78
+ # Notifier.welcome(User.first).deliver_later(wait_until: 10.hours.from_now)
79
+ #
80
+ # Options:
81
+ #
82
+ # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay.
83
+ # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time.
84
+ # * <tt>:queue</tt> - Enqueue the email on the specified queue.
85
+ #
86
+ # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
87
+ # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
88
+ # +delivery_job+.
89
+ #
90
+ # class AccountRegistrationMailer < ApplicationMailer
91
+ # self.delivery_job = RegistrationDeliveryJob
92
+ # end
93
+ def deliver_later(options = {})
94
+ enqueue_delivery :deliver_now, options
95
+ end
96
+
97
+ # Delivers an email without checking +perform_deliveries+ and +raise_delivery_errors+,
98
+ # so use with caution.
99
+ #
100
+ # Notifier.welcome(User.first).deliver_now!
101
+ #
102
+ def deliver_now!
103
+ processed_mailer.handle_exceptions do
104
+ message.deliver!
105
+ end
106
+ end
107
+
108
+ # Delivers an email:
109
+ #
110
+ # Notifier.welcome(User.first).deliver_now
111
+ #
112
+ def deliver_now
113
+ processed_mailer.handle_exceptions do
114
+ message.deliver
115
+ end
116
+ end
117
+
118
+ private
119
+ # Returns the processed Mailer instance. We keep this instance
120
+ # on hand so we can delegate exception handling to it.
121
+ def processed_mailer
122
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
123
+ mailer.process @action, *@args
124
+ end
125
+ end
126
+
127
+ def enqueue_delivery(delivery_method, options = {})
128
+ if processed?
129
+ ::Kernel.raise "You've accessed the message before asking to " \
130
+ "deliver it later, so you may have made local changes that would " \
131
+ "be silently lost if we enqueued a job to deliver it. Why? Only " \
132
+ "the mailer method *arguments* are passed with the delivery job! " \
133
+ "Do not access the message in any way if you mean to deliver it " \
134
+ "later. Workarounds: 1. don't touch the message before calling " \
135
+ "#deliver_later, 2. only touch the message *within your mailer " \
136
+ "method*, or 3. use a custom Active Job instead of #deliver_later."
137
+ else
138
+ job = @mailer_class.delivery_job
139
+ args = arguments_for(job, delivery_method)
140
+ job.set(options).perform_later(*args)
141
+ end
142
+ end
143
+
144
+ def arguments_for(delivery_job, delivery_method)
145
+ if delivery_job <= MailDeliveryJob
146
+ [@mailer_class.name, @action.to_s, delivery_method.to_s, args: @args]
147
+ else
148
+ [@mailer_class.name, @action.to_s, delivery_method.to_s, *@args]
149
+ end
150
+ end
151
+ end
152
+ end