require 'action_mailer'
#
# Mailer Behavior for Radiant
# created by: M@ McCray - elucidata.net/mattmccray.com
# version: 0.2.1
# contact: darthapo@gmail.com
class MailerBehavior < Behavior::Base
class MailerTagError < StandardError; end
register "Mailer"
description %{
The Mailer behavior enables form mail on a page. You can define email
templates using pages parts (email, and/or email_html). You configure
the recipients and other Mailer settings in a config part.
}
attr_reader :form_name, :form_conf, :form_error, :form_data, :tag_attr
# Page processing. If the page has posted-back, it will try to deliver the emails
# and redirect to a different page, if specified.
def process(request, response)
@request, @response = request, response
@form_name, @form_error = nil, nil
if request.post?
@form_name = request.parameters[:mailer_name]
@form_conf = page_config['mailers'][form_name].symbolize_keys || {}
@form_data = request.parameters[:mailer]
# If there are recipients defined, send email...
if form_conf.has_key? :recipients
if send_mail and form_conf.has_key? :redirect_to
response.redirect( form_conf[:redirect_to] )
else
super(request, response)
end
else
@form_error = "Email wasn't sent because no recipients are defined"
super(request, response)
end
else
super(request, response)
end
end
# We need to process the page everytime, so that we can send the email!
def cache_page?
false
end
# Mailer Tags:
define_tags do
url = request.request_uri unless request.nil?
# This is just for creating the namespace.
tag "mailer" do |tag|
tag.expand
end
# The ... tag is required.
# It should encompass all of the helper tags for creating form fields.
tag "mailer:form" do |tag|
@tag_attr = { :class=>get_class_name('form') }.update( tag.attr.symbolize_keys )
raise_error_if_name_missing 'mailer:form'
# Build the html form tag...
results = %Q(
)
end
# Build tags for all of the tags...
%w(text password file submit reset checkbox radio hidden).each do |type|
tag "mailer:#{type}" do |tag|
@tag_attr = tag.attr.symbolize_keys
raise_error_if_name_missing "mailer:#{type}" unless %(submit reset).include? type
input_tag_html( type )
end
end
# A tag. This is compatible with the tag as well
tag 'mailer:select' do |tag|
@tag_attr = { :id=>tag.attr['name'], :class=>get_class_name('select'), :size=>'1' }.update( tag.attr.symbolize_keys )
raise_error_if_name_missing "mailer:select"
tag.locals.parent_tag_name = tag_attr[:name]
tag.locals.parent_tag_type = 'select'
results = %Q("
end
# A tag, of course
tag 'mailer:textarea' do |tag|
@tag_attr = { :id=>tag.attr['name'], :class=>get_class_name('textarea'), :rows=>'5', :cols=>'35' }.update( tag.attr.symbolize_keys )
raise_error_if_name_missing "mailer:textarea"
results = %Q("
end
# Special tag for radio groups. This one works with the tag
tag 'mailer:radiogroup' do |tag|
@tag_attr = tag.attr.symbolize_keys
raise_error_if_name_missing "mailer:radiogroup"
tag.locals.parent_tag_name = tag_attr[:name]
tag.locals.parent_tag_type = 'radiogroup'
tag.expand
end
# This is a custom tag that will render and tag if the parent is a
# tag, or it will render an tag if
# the parent is a ...
tag 'mailer:option' do |tag|
@tag_attr = tag.attr.symbolize_keys
raise_error_if_name_missing "mailer:option"
result = ""
if tag.locals.parent_tag_type == 'select'
result << %Q||
elsif tag.locals.parent_tag_type == 'radiogroup'
tag.globals.option_count = tag.globals.option_count.nil? ? 1 : tag.globals.option_count += 1
options = tag_attr.clone.update({
:id => "#{tag.locals.parent_tag_name}_#{tag.globals.option_count}",
:value => tag_attr[:value] || tag_attr[:name],
:name => tag.locals.parent_tag_name
})
result << input_tag_html( 'radio', options )
result << %Q||
end
end
# For use with email template parts -- retrieves the data posted by the form
tag 'mailer:get' do |tag|
name = tag.attr['name']
if name
form_data[name]
else
form_data.to_hash.to_yaml.to_s
end
end
end
protected
# Since several form tags use the format, let's do that work in one place
def input_tag_html(type, opts=tag_attr)
options = { :id => tag_attr[:name], :value => "", :class=>get_class_name(type) }.update(opts)
results = %Q("
end
def add_attrs_to(results, tag_attrs=tag_attr)
# Well, turns out I stringify the keys so I can sort them so I can test the tag output
tag_attrs.stringify_keys.sort.each do |name, value|
results << %Q(#{name.to_s}="#{value.to_s}" ) unless name == 'name'
end
results
end
# Get the default css class based on type
def get_class_name(type, class_name=nil)
class_name = 'mailer-form' if class_name.nil? and %(form).include? type
class_name = 'mailer-field' if class_name.nil? and %(text password file select textarea).include? type
class_name = 'mailer-button' if class_name.nil? and %(submit reset).include? type
class_name = 'mailer-option' if class_name.nil? and %(checkbox radio).include? type
class_name
end
# Raises a 'name missing' tag error
def raise_name_error(tag_name)
raise MailerTagError.new( "`#{tag_name}' tag requires a `name' attribute" )
end
def raise_error_if_name_missing(tag_name)
raise_name_error( tag_name ) if tag_attr[:name].nil? or tag_attr[:name].empty?
end
# Does the work of actually sending the emails
def send_mail()
begin
# Data defined in config part
recipients = form_conf[:recipients]
from = form_conf[:from] || "no-reply@#{request.host}"
# Render the email templates from page parts
plain_body = @page.part( :email ) ? render_page_part( :email ) : render_page_part( :email_plain )
html_body = render_page_part( :email_html ) || nil
# If we haven't defined any kind of mail template, use a default text one.
if (plain_body.nil? or plain_body.empty?) and (html_body.nil? or html_body.empty?)
plain_body = <<-EMAIL
The following information was posted:
#{form_data.to_hash.to_yaml}
EMAIL
end
# Since we can't have a subclass of ActionMailer in our behavior file,
# We add a generic mailer method to the ActionMailer::Base clase.
# Is this a hack? Yes. Does it work? Yes.
ActionMailer::Base.module_eval( <<-CODE ) unless ActionMailer::Base.respond_to? 'generic_mailer'
def generic_mailer(options)
@recipients = options[:recipients]
@from = options[:from] || ""
@cc = options[:cc] || ""
@bcc = options[:bcc] || ""
@subject = options[:subject] || ""
@headers = options[:headers] || {}
@charset = options[:charset] || "utf-8"
if options.has_key? :html_body and !options[:html_body].nil?
part "text/html" do |a|
a.body = options[:html_body] || ""
end
end
if options.has_key? :plain_body
part "text/plain" do |p|
p.body = options[:plain_body] || ""
end
end
end
CODE
# Now deliver mail using our new generic_mail method
ActionMailer::Base.deliver_generic_mailer(
:recipients => recipients,
:from => from,
:subject => form_data[:subject] || "Form Mail from #{request.host}",
:plain_body => plain_body,
:html_body => html_body
)
rescue
@form_error = "Error encountered while trying to send email. #{$!}"
return false
end
true
end
end