actionmailer 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionmailer might be problematic. Click here for more details.

@@ -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