radiant-mailer-extension 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,331 @@
1
+ module MailerTags
2
+ include Radiant::Taggable
3
+ include ActionView::Helpers::DateHelper
4
+
5
+ def config
6
+ @config ||= begin
7
+ page = self
8
+ until page.part(:mailer) or (not page.parent)
9
+ page = page.parent
10
+ end
11
+ string = page.render_part(:mailer)
12
+ (string.empty? ? {} : YAML::load(string))
13
+ end
14
+ end
15
+
16
+ desc %{ All mailer-related tags live inside this one. }
17
+ tag "mailer" do |tag|
18
+ if Mail.valid_config?(config)
19
+ tag.expand
20
+ else
21
+ "Mailer config is not valid (see Mailer.valid_config?)"
22
+ end
23
+ end
24
+
25
+ desc %{
26
+ Will expand if and only if there is an error with the last mail.
27
+
28
+ If you specify the "on" attribute, it will only expand if there
29
+ is an error on the named attribute, and will make the error
30
+ message available to the mailer:error:message tag.}
31
+ tag "mailer:if_error" do |tag|
32
+ if mail = tag.locals.page.last_mail
33
+ if on = tag.attr['on']
34
+ if error = mail.errors[on]
35
+ tag.locals.error_message = error
36
+ tag.expand
37
+ end
38
+ else
39
+ unless mail.valid?
40
+ tag.expand
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ desc %{
47
+ Will expand if and only if there is NO error with the last mail.
48
+
49
+ If you specify the "on" attribute, it will only expand if there
50
+ is NO error on the named attribute.}
51
+ tag "mailer:unless_error" do |tag|
52
+ if mail = tag.locals.page.last_mail
53
+ if on = tag.attr['on']
54
+ unless mail.errors[on]
55
+ tag.expand
56
+ end
57
+ else
58
+ if mail.valid?
59
+ tag.expand
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ desc %{Outputs the error message.}
66
+ tag "mailer:if_error:message" do |tag|
67
+ tag.locals.error_message
68
+ end
69
+
70
+ desc %{
71
+ Generates a form for submitting email.
72
+
73
+ Usage:
74
+ <pre><code> <r:mailer:form>...</r:mailer:form></code></pre>}
75
+ tag "mailer:form" do |tag|
76
+ tag.attr['id'] ||= 'mailer'
77
+ results = []
78
+ action = Radiant::Config['mailer.post_to_page?'] ? tag.locals.page.url : "/pages/#{tag.locals.page.id}/mail##{tag.attr['id']}"
79
+ results << %(<form action="#{action}" method="post" #{mailer_attrs(tag)}>)
80
+ results << tag.expand
81
+ results << %(</form>)
82
+ end
83
+
84
+ desc %{
85
+ Outputs a bit of javascript that will cause the enclosed content
86
+ to be displayed when mail is successfully sent.}
87
+ tag "mailer:if_success" do |tag|
88
+ tag.expand if tag.locals.page.last_mail && tag.locals.page.last_mail.sent?
89
+ end
90
+
91
+ %w(checkbox date datetime datetime-local email hidden month number radio range tel text time url week).each do |type|
92
+ desc %{
93
+ Renders a #{type} input tag for a mailer form. The 'name' attribute is required.}
94
+ tag "mailer:#{type}" do |tag|
95
+ raise_error_if_name_missing "mailer:#{type}", tag.attr
96
+ value = (prior_value(tag) || tag.attr['value'])
97
+ result = [%(<input type="#{type}" value="#{value}" #{mailer_attrs(tag)}>)]
98
+ add_required(result, tag)
99
+ end
100
+ end
101
+
102
+ desc %{
103
+ Renders a @<select>...</select>@ tag for a mailer form. The 'name' attribute is required.
104
+ @<r:option />@ tags may be nested inside the tag to automatically generate options.}
105
+ tag 'mailer:select' do |tag|
106
+ raise_error_if_name_missing "mailer:select", tag.attr
107
+ tag.locals.parent_tag_name = tag.attr['name']
108
+ tag.locals.parent_tag_type = 'select'
109
+ result = [%Q(<select #{mailer_attrs(tag, 'size' => '1')}>)]
110
+ result << tag.expand
111
+ result << "</select>"
112
+ add_required(result, tag)
113
+ end
114
+
115
+ desc %{
116
+ Renders a <textarea>...</textarea> tag for a mailer form. The 'name' attribute is required. }
117
+ tag 'mailer:textarea' do |tag|
118
+ raise_error_if_name_missing "mailer:textarea", tag.attr
119
+ result = [%(<textarea #{mailer_attrs(tag, 'rows' => '5', 'cols' => '35')}>)]
120
+ result << (prior_value(tag) || tag.expand)
121
+ result << "</textarea>"
122
+ add_required(result, tag)
123
+ end
124
+
125
+ desc %{
126
+ Renders a series of @<input type="radio" .../>@ tags for a mailer form. The 'name' attribute is required.
127
+ Nested @<r:option />@ tags will generate individual radio buttons with corresponding values. }
128
+ tag 'mailer:radiogroup' do |tag|
129
+ raise_error_if_name_missing "mailer:radiogroup", tag.attr
130
+ tag.locals.parent_tag_name = tag.attr['name']
131
+ tag.locals.parent_tag_type = 'radiogroup'
132
+ tag.expand
133
+ end
134
+
135
+ desc %{ Renders an @<option/>@ tag if the parent is a
136
+ @<r:mailer:select>...</r:mailer:select>@ tag, an @<input type="radio"/>@ tag if
137
+ the parent is a @<r:mailer:radiogroup>...</r:mailer:radiogroup>@ }
138
+ tag 'mailer:option' do |tag|
139
+ if tag.locals.parent_tag_type == 'radiogroup'
140
+ tag.attr['name'] ||= tag.locals.parent_tag_name
141
+ end
142
+ value = (tag.attr['value'] || tag.expand)
143
+ prev_value = prior_value(tag, tag.locals.parent_tag_name)
144
+ checked = tag.attr.delete('selected') || tag.attr.delete('checked')
145
+ selected = prev_value ? prev_value == value : checked
146
+
147
+ if tag.locals.parent_tag_type == 'select'
148
+ %(<option value="#{value}"#{%( selected="selected") if selected} #{mailer_attrs(tag)}>#{tag.expand}</option>)
149
+ elsif tag.locals.parent_tag_type == 'radiogroup'
150
+ %(<input type="radio" value="#{value}"#{%( checked="checked") if selected} #{mailer_attrs(tag)}>)
151
+ end
152
+ end
153
+
154
+ desc %{
155
+ Provides a mechanism to iterate over array datum submitted via a
156
+ mailer form. Used in the 'email', 'email_html', and 'mailer' parts to
157
+ generate the resulting email. May work OK nested, but this hasn't been
158
+ tested.
159
+ }
160
+ tag 'mailer:get_each' do |tag|
161
+ name = tag.attr['name']
162
+ mail = tag.locals.page.last_mail
163
+ if tag.locals.mailer_element then
164
+ ary=tag.locals.mailer_element
165
+ else
166
+ ary=mail.data[name]
167
+ end
168
+ result=[]
169
+ return '' if ary.blank?
170
+
171
+ case ary
172
+ when Array
173
+ ary.each_with_index do |element, idx|
174
+ tag.locals.mailer_key=idx
175
+ tag.locals.mailer_element = element
176
+ result << tag.expand
177
+ end
178
+ else
179
+ ary.each do |key, element|
180
+ tag.locals.mailer_key=key
181
+ tag.locals.mailer_element = element
182
+ result << tag.expand
183
+ end
184
+ end
185
+ result
186
+ end
187
+
188
+ desc %{
189
+ Uses @ActionView::Helpers::DateHelper.date_select@ to render three select tags for date selection.
190
+ }
191
+ tag 'mailer:date_select' do |tag|
192
+ raise_error_if_name_missing "mailer:date_select", tag.attr
193
+ name = tag.attr.delete('name')
194
+
195
+ options = {}
196
+
197
+ tag.attr.each do |k, v|
198
+ if v =~ /(true|false)/
199
+ options[k] = (v == 'true')
200
+ elsif v =~ /\d+/
201
+ options[k] = v.to_i
202
+ elsif k == 'order'
203
+ options[k] = v.split(',').map(&:strip).map(&:to_sym)
204
+ else
205
+ options[k] = v
206
+ end
207
+ end
208
+
209
+ options.symbolize_keys!
210
+
211
+ date_select('mailer', name, options)
212
+ end
213
+
214
+ desc %{
215
+ Renders the value of a datum submitted via a mailer form. Used in the
216
+ 'email', 'email_html', and 'mailer' parts to generate the resulting email.
217
+ When used within mailer:get_each it defaults to getting elements within
218
+ that array.
219
+ }
220
+ tag 'mailer:get' do |tag|
221
+ name = tag.attr['name']
222
+ mail = tag.locals.page.last_mail
223
+ if tag.locals.mailer_element then
224
+ element = tag.locals.mailer_element
225
+ else
226
+ element = tag.locals.page.last_mail.data
227
+ end
228
+ if name
229
+ format_mailer_data(element, name)
230
+ else
231
+ element.to_hash.to_yaml.to_s
232
+ end
233
+ end
234
+
235
+ desc %{
236
+ For use within a mailer:get_each to output the index/key for each element
237
+ of the hash.
238
+ }
239
+ tag 'mailer:index' do |tag|
240
+ tag.locals.mailer_key || nil
241
+ end
242
+
243
+ desc %{
244
+ Renders the contained block if a named datum was submitted via a mailer
245
+ form. Used in the 'email', 'email_html' and 'mailer' parts to generate
246
+ the resulting email.
247
+ }
248
+ tag 'mailer:if_value' do |tag|
249
+ name = tag.attr['name']
250
+ eq = tag.attr['equals']
251
+ mail = tag.locals.page.last_mail || tag.globals.page.last_mail
252
+ tag.expand if name && mail.data[name] && (eq.blank? || eq == mail.data[name])
253
+ end
254
+
255
+ def format_mailer_data(element, name)
256
+ data = element[name]
257
+ if Array === data
258
+ data.to_sentence
259
+ elsif date = detect_date(element, name)
260
+ date
261
+ else
262
+ data
263
+ end
264
+ end
265
+
266
+ def detect_date(mail, name)
267
+ date_components = mail.select { |key, value| key =~ Regexp.new("#{name}\\(\\di\\)") }
268
+
269
+ if date_components.length == 3
270
+ date_values = date_components.sort { |a, b| a[0] <=> b[0] }.map { |v| v[1].to_i }
271
+ return Date.new(*date_values)
272
+ else
273
+ return nil
274
+ end
275
+ end
276
+
277
+ def prior_value(tag, tag_name=tag.attr['name'])
278
+ if mail = tag.locals.page.last_mail
279
+ mail.data[tag_name]
280
+ else
281
+ nil
282
+ end
283
+ end
284
+
285
+ def mailer_attrs(tag, extras={})
286
+ attrs = {
287
+ 'accept' => nil,
288
+ 'accesskey' => nil,
289
+ 'alt' => nil,
290
+ 'autocomplete' => nil,
291
+ 'autofocus' => nil,
292
+ 'checked' => nil,
293
+ 'class' => nil,
294
+ 'contextmenu' => nil,
295
+ 'dir' => nil,
296
+ 'disabled' => nil,
297
+ 'height' => nil,
298
+ 'hidden' => nil,
299
+ 'id' => tag.attr['name'],
300
+ 'lang' => nil,
301
+ 'max' => nil,
302
+ 'maxlength' => nil,
303
+ 'min' => nil,
304
+ 'pattern' => nil,
305
+ 'placeholder' => nil,
306
+ 'required' => nil,
307
+ 'size' => nil,
308
+ 'spellcheck' => nil,
309
+ 'step' => nil,
310
+ 'style' => nil,
311
+ 'tabindex' => nil,
312
+ 'title' => nil,
313
+ 'width' => nil}.merge(extras)
314
+ result = attrs.collect do |k,v|
315
+ v = (tag.attr[k] || v)
316
+ next if v.blank?
317
+ %(#{k}="#{v}")
318
+ end.reject{|e| e.blank?}
319
+ result << %(name="mailer[#{tag.attr['name']}]") unless tag.attr['name'].blank?
320
+ result.join(' ')
321
+ end
322
+
323
+ def add_required(result, tag)
324
+ result << %(<input type="hidden" name="mailer[required][#{tag.attr['name']}]" value="#{tag.attr['required']}">) if tag.attr['required']
325
+ result
326
+ end
327
+
328
+ def raise_error_if_name_missing(tag_name, tag_attr)
329
+ raise "`#{tag_name}' tag requires a `name' attribute" if tag_attr['name'].blank?
330
+ end
331
+ end
File without changes
@@ -0,0 +1,16 @@
1
+ namespace :radiant do
2
+ namespace :extensions do
3
+ namespace :mailer do
4
+
5
+ desc "Runs the migration of the Mailer extension"
6
+ task :migrate => :environment do
7
+ require 'radiant/extension_migrator'
8
+ if ENV["VERSION"]
9
+ MailerExtension.migrator.migrate(ENV["VERSION"].to_i)
10
+ else
11
+ MailerExtension.migrator.migrate
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ class MailerExtension < Radiant::Extension
2
+ version "1.0"
3
+ description "Provides support for email forms and generic mailing functionality."
4
+ url "http://github.com/radiant/radiant-mailer-extension"
5
+
6
+ define_routes do |map|
7
+ map.resources :mail, :path_prefix => "/pages/:page_id", :controller => "mail"
8
+ end
9
+
10
+ def activate
11
+ Page.class_eval do
12
+ include MailerTags
13
+ include MailerProcess
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,71 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{radiant-mailer-extension}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Nathaniel Talbott", "Sean Cribbs", "Matt McCray"]
12
+ s.date = %q{2010-04-29}
13
+ s.description = %q{An extension for Radiant CMS that allows you to create 'contact us' and other mail-bound forms.}
14
+ s.email = %q{radiant@radiantcms.org}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "app/controllers/mail_controller.rb",
26
+ "app/models/mail.rb",
27
+ "app/models/mailer.rb",
28
+ "lib/mailer_process.rb",
29
+ "lib/mailer_tags.rb",
30
+ "lib/radiant-mailer-extension.rb",
31
+ "lib/tasks/mailer_extension_tasks.rake",
32
+ "mailer_extension.rb",
33
+ "radiant-mailer-extension.gemspec",
34
+ "spec/controllers/mail_controller_spec.rb",
35
+ "spec/dataset/mailer_dataset.rb",
36
+ "spec/lib/mailer_process_spec.rb",
37
+ "spec/lib/mailer_tags_spec.rb",
38
+ "spec/models/mail_spec.rb",
39
+ "spec/models/mailer_spec.rb",
40
+ "spec/spec.opts",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/radiant/radiant-mailer-extension}
44
+ s.rdoc_options = ["--charset=UTF-8"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = %q{1.3.6}
47
+ s.summary = %q{Enables form mail on a page.}
48
+ s.test_files = [
49
+ "spec/controllers/mail_controller_spec.rb",
50
+ "spec/dataset/mailer_dataset.rb",
51
+ "spec/lib/mailer_process_spec.rb",
52
+ "spec/lib/mailer_tags_spec.rb",
53
+ "spec/models/mail_spec.rb",
54
+ "spec/models/mailer_spec.rb",
55
+ "spec/spec_helper.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ s.add_development_dependency(%q<rspec>, [">= 0"])
64
+ else
65
+ s.add_dependency(%q<rspec>, [">= 0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<rspec>, [">= 0"])
69
+ end
70
+ end
71
+
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe MailController do
4
+ dataset :mailer
5
+ describe "POST to /pages/:id/mail" do
6
+ before :each do
7
+ @page = pages(:mail_form)
8
+ @mail = mock("Mail", :send => false)
9
+ ActionMailer::Base.delivery_method = :test
10
+ ActionMailer::Base.deliveries = []
11
+ end
12
+
13
+ def do_post
14
+ post :create, :page_id => @page.id, :mailer => {:body => 'Hello, world!'}
15
+ end
16
+
17
+ it "should load the page by id" do
18
+ do_post
19
+ assigns[:page].should == @page
20
+ end
21
+
22
+ it "should render the page if mail fails to send" do
23
+ Mail.should_receive(:new).and_return(@mail)
24
+ @mail.should_receive(:send).and_return(false)
25
+ @controller.should_not_receive(:redirect_to)
26
+ do_post
27
+ response.should be_success
28
+ response.body.should == assigns[:page].render
29
+ end
30
+
31
+ it "should redirect back to the page by default if the mail sends" do
32
+ Mail.should_receive(:new).and_return(@mail)
33
+ @mail.should_receive(:send).and_return(true)
34
+ do_post
35
+ response.should be_redirect
36
+ response.redirect_url.should match(%r{/mail-form/#mail_sent})
37
+ end
38
+
39
+ it "should redirect to the configured url if the mail sends" do
40
+ @page.part(:mailer).update_attributes(:content => {'redirect_to' => '/first', 'recipients' => 'foo@bar.com', 'from' => 'baz@noreply.com'}.to_yaml)
41
+ Mail.should_receive(:new).and_return(@mail)
42
+ @mail.should_receive(:send).and_return(true)
43
+ do_post
44
+ response.should be_redirect
45
+ response.redirect_url.should match(%r{/first})
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ class MailerDataset < Dataset::Base
2
+ uses :pages
3
+
4
+ def load
5
+ create_page "Mail form" do
6
+ create_page_part "mailer", :content => {'recipients' => 'foo@bar.com', 'from' => 'baz@noreply.com'}.to_yaml
7
+ end
8
+ create_page "Plain mail" do
9
+ create_page_part "plain_mailer", :content => {'recipients' => 'foo@bar.com', 'from' => 'baz@noreply.com'}.to_yaml, :name => "mailer"
10
+ create_page_part "email_plain", :content => 'The body: <r:mailer:get name="body" />', :name => 'email'
11
+ end
12
+ create_page "HTML mail" do
13
+ create_page_part "html_mailer", :content => {'recipients' => 'foo@bar.com', 'from' => 'baz@noreply.com'}.to_yaml, :page_id => page_id(:html_mail), :name => "mailer"
14
+ create_page_part "email_html", :content => '<html><body><r:mailer:get name="body" /></body></html>', :page_id => page_id(:html_mail)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,80 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+ require 'site_controller'
3
+ SiteController.class_eval { def rescue_action(e) raise e; end }
4
+
5
+ describe "MailerProcess" do
6
+ it "should add a last_mail accessor to Page" do
7
+ @page = Page.new
8
+ @page.should respond_to(:last_mail)
9
+ @page.should respond_to(:last_mail=)
10
+ end
11
+
12
+ it "should override the process method" do
13
+ @page = Page.new
14
+ @page.should respond_to(:process_without_mailer)
15
+ @page.should respond_to(:process_with_mailer)
16
+ end
17
+ end
18
+
19
+ describe SiteController, "receiving a mailer request", :type => :controller do
20
+ dataset :mailer
21
+
22
+ before :each do
23
+ ResponseCache.instance.clear
24
+ Radiant::Config['mailer.post_to_page?'] = true
25
+ @page = pages(:mail_form)
26
+ @mail = mock("Mail", :send => false, :data => {}, :errors => {})
27
+ Page.stub!(:find_by_url).and_return(@page)
28
+ Mail.stub!(:new).and_return(@mail)
29
+ end
30
+
31
+ it "should not process a mail form if the request was GET" do
32
+ @page.should_receive(:process_without_mailer).with(request, response)
33
+ Mail.should_not_receive(:new)
34
+ get :show_page, :url => @page.url
35
+ end
36
+
37
+ it "should not process a mail form if mailer.post_to_page? is set to false" do
38
+ Radiant::Config['mailer.post_to_page?'] = false
39
+ @page.should_receive(:process_without_mailer).with(request, response)
40
+ Mail.should_not_receive(:new)
41
+ post :show_page, :url => @page.url
42
+ end
43
+
44
+ it "should not process a mail form unless there are mailer parameters" do
45
+ @page.should_receive(:process_without_mailer).with(request, response)
46
+ Mail.should_not_receive(:new)
47
+ post :show_page, :url => @page.url
48
+ end
49
+
50
+ it "should process a mail form if the request was POST, posting to the page is enabled, and mailer parameters were submitted" do
51
+ Mail.should_receive(:new).and_return(@mail)
52
+ @page.should_receive(:process_without_mailer).with(request, response)
53
+ post :show_page, :url => @page.url, :mailer => {:foo => 'bar'}
54
+ end
55
+
56
+ it "should create a Mail object and assign it to the page's last_mail accessor" do
57
+ Mail.should_receive(:new).and_return(@mail)
58
+ @page.should_receive(:last_mail=).with(@mail).at_least(:once)
59
+ post :show_page, :url => @page.url, :mailer => {:foo => 'bar'}
60
+ end
61
+
62
+ it "should attempt to send the mail" do
63
+ @mail.should_receive(:send).and_return(false)
64
+ post :show_page, :url => @page.url, :mailer => {:foo => 'bar'}
65
+ end
66
+
67
+ it "should clear out the mail data and errors when sending is successful" do
68
+ @mail.should_receive(:send).and_return(true)
69
+ @mail.data.should_receive(:delete_if)
70
+ @mail.errors.should_receive(:delete_if)
71
+ post :show_page, :url => @page.url, :mailer => {:foo => 'bar'}
72
+ end
73
+
74
+ it "should redirect to the configured URL when sending is successful" do
75
+ @page.should_receive(:mailer_config_and_page).and_return([{:redirect_to => "/foo/bar"}, @page])
76
+ @mail.should_receive(:send).and_return(true)
77
+ post :show_page, :url => @page.url, :mailer => {:foo => 'bar'}
78
+ response.redirect_url.should match(%r{/foo/bar})
79
+ end
80
+ end