radiant-mailer-extension 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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