radiant-mailer-extension 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  The Mailer extension enables form mail on a page.
4
4
 
5
-
6
5
  == Usage
7
6
 
8
7
  You can define email templates using pages parts (email, and/or email_html).
@@ -17,15 +16,21 @@ You configure the recipients and other Mailer settings in a "mailer" part:
17
16
 
18
17
  The following tags are available to help you build the form:
19
18
 
20
- <r:mailer:form name=""> ... </r:mailer:form>
21
- <r:mailer:text name="" />
19
+ <r:mailer:form name=""> ... </r:mailer:form>
20
+ <r:mailer:text name="" />
21
+ <r:mailer:password name="" />
22
+ <r:mailer:reset name="" />
22
23
  <r:mailer:checkbox name="" />
23
24
  <r:mailer:radio name="" />
25
+ <r:mailer:file name="" />
24
26
  <r:mailer:radiogroup name=""> ... </r:mailer:radiogroup>
25
27
  <r:mailer:select name=""> ... </r:mailer:select>
26
28
  <r:mailer:date_select name=""/>
27
29
  <r:mailer:textarea name=""> ... </r:mailer:textarea>
28
30
  <r:mailer:option name="" />
31
+ <r:mailer:submit name="" />
32
+ <r:mailer:image name="" />
33
+ <r:mailer:submit_placeholder />
29
34
 
30
35
  When processing the form (in email and email_html), the following tags are
31
36
  available:
@@ -42,11 +47,130 @@ Simple example of a form:
42
47
  <r:mailer:text name="name" /> <br/>
43
48
  Message:<br/>
44
49
  <r:mailer:textarea name="message" /> <br/>
45
- <input type="submit" value="Send" />
50
+ <r:mailer:submit name="Send" />
51
+ </r:mailer:form>
52
+
53
+ === Required fields
54
+
55
+ You can specify fields which must be populated or the form will be invalid and will redisplay the page with an error informing the user to populate those fields.
56
+
57
+ Simple example of a required field:
58
+
59
+ <r:mailer:form>
60
+ ...
61
+ Name:<br/>
62
+ <r:mailer:text name="name" required="true"/> <br/>
63
+ ...
64
+ <r:mailer:submit name="Send" />
65
+ </r:mailer:form>
66
+
67
+ Simple example of a required field with user-defined message:
68
+
69
+ <r:mailer:form>
70
+ ...
71
+ Name:<br/>
72
+ <r:mailer:text name="name" required="should not be blank"/> <br/>
73
+ ...
74
+ <r:mailer:submit name="Send" />
75
+ </r:mailer:form>
76
+
77
+ You can also specify fields which must be validated 'as_email' (i.e. a@b.com).
78
+
79
+ Simple example of a required field with email address validation:
80
+
81
+ <r:mailer:form>
82
+ ...
83
+ Reply-To:<br/>
84
+ <r:mailer:text name="reply_email" required="as_email"/> <br/>
85
+ ...
86
+ <r:mailer:submit name="Send" />
46
87
  </r:mailer:form>
47
88
 
89
+ You can also specify fields which must be validated as defined regexp (i.e. /^\d{2}\.\d{2}\.\d{4}\$/ for date dd.mm.yyyy).
48
90
 
49
- == User-provided Configuration
91
+
92
+ Simple example of a required field with regexp validation:
93
+
94
+ <r:mailer:form>
95
+ ...
96
+ Birthday:<br/>
97
+ <r:mailer:text name="birthday" required="/^\d{2}\.\d{2}\.\d{4}\$/"/> <br/>
98
+ ...
99
+ <r:mailer:submit name="Send" />
100
+ </r:mailer:form>
101
+
102
+ Finally, you can put all field validations in the "mailer" part:
103
+
104
+ subject: From the website of Whatever
105
+ from: noreply@example.com
106
+ redirect_to: /contact/thank-you
107
+ recipients:
108
+ - one@one.com
109
+ required:
110
+ name: "true"
111
+ email: as_email
112
+ message: "true"
113
+
114
+ The field names above are "name," "email," and "message." Note the quotation marks around true values. If you do your field validations this way, Mailer will ignore any validations you attempt through your radius tags. This method of validation keeps Mailer from adding hidden inputs to keep track of required fields. See the caveat below.
115
+
116
+
117
+ === Spam blocking
118
+
119
+ You can specify which fields may not contain anything that looks like a link in the "mailer" part. For example:
120
+
121
+ subject: From the website of Whatever
122
+ from: noreply@example.com
123
+ redirect_to: /contact/thank-you
124
+ recipients:
125
+ - one@one.com
126
+ disallow_links:
127
+ - comments
128
+ - questions
129
+
130
+ The comments and questions fields would throw an error if the user or a spam bot entered the following phrases: "www", "&amp;", "http:", "mailto:", "bcc:", "href", "multipart", "[url", or "Content-Type:".
131
+
132
+
133
+ You can also include one field on your form that must be left blank. If anyone enters something in the field, the field throws an error. The tactic here is to hide the field from human readers, but to leave the field visible to spam bots. Here is how you would edit the "mailer" part to implement this:
134
+
135
+ subject: From the website of Whatever
136
+ from: noreply@example.com
137
+ redirect_to: /contact/thank-you
138
+ recipients:
139
+ - one@one.com
140
+ leave_blank: your_field_name
141
+
142
+ "your_field_name" is the name of the field you want to hide. It is up to you to hide the field when you construct your form. I would recommend against using a traditional hidden input field. Use style="display:none" instead.
143
+
144
+
145
+ === File attachments
146
+
147
+ In many cases it is desirable to limit the maximum size of a file that may be uploaded. This is set as the max_filesize attribute for mailers in the mailer page part. Any file included in the form will have the limit imposed. Following is a simple example mailer part that includes a file size limit of 100,000 bytes:
148
+
149
+ subject: From the website of Whatever
150
+ from: noreply@mydomain.com
151
+ redirect_to: /contact/thank-you
152
+ max_filesize: 100000
153
+ recipients:
154
+ - one@one.com
155
+ - two@two.com
156
+
157
+ The following is a simple form that might be used to submit a file for the above configuration:
158
+
159
+ <r:mailer:form name="contact">
160
+ Type your message: <r:mailer:text name="themessage" /> <br/>
161
+ Select a file: <r:mailer:file name="thefile" /> <br/>
162
+ <r:mailer:submit value="submit"/>
163
+ </r:mailer:form>
164
+
165
+ If a user does not select a file the other form contents will still be e-mailed. The <r:mailer:get name="foo" /> (with <r:mailer:file name="foo" />) will provide the uploaded file name.
166
+
167
+ If you are using email or email_html parts then the <r:mailer:get name="" /> tag can be used to retrieve the name of the uploaded file. If no file was uploaded "" will be returned.
168
+
169
+ === Submit placeholder
170
+
171
+ If you wish to show that activity is taking place during submission you may use the <r:mailer:submit_placeholder /> tag in your form. This will insert a hidden div with the contents of the submit_placeholder page part. The div will be displayed when the user clicks any submit button.
172
+
173
+ === User-provided Configuration
50
174
 
51
175
  Sometimes, rather than explicitly configuring the recipients and such in the mailer part, you'd rather have them passed in by the person submitting the form. Mailer supports this by allowing you to specify a form field to pull the value from:
52
176
 
@@ -56,7 +180,6 @@ Then you just have to add that field to your mailer form and you're all set.
56
180
 
57
181
  This is supported for the from (from_field), recipients (recipients_field) and reply_to (reply_to_field) properties.
58
182
 
59
-
60
183
  == Enabling action_mailer
61
184
 
62
185
  In environment.rb you'll probably need to change:
@@ -67,11 +190,19 @@ to:
67
190
 
68
191
  config.frameworks -= []
69
192
 
193
+ == Updating from the older mailer extension
194
+
195
+ If you get this error "The single-table inheritance mechanism failed to locate the subclass: 'MailerPage'.", run 'rake:radiant:extensions:mailer:migrate'. This will change all pages with a MailerPage classname into regular pages.
196
+ Second, your 'config' page part has to be renamed to 'mailer', and the first two YAML levels should be deleted (see instructions above).
197
+
198
+ If you are getting a stack level too deep error, it may be caused by using <r:mailer:get /> in your 'mailer' part.
199
+ Use the from_field or other options to get to the email adress that was posted (see User-provided configuration).
70
200
 
71
201
  == Caveats
72
202
 
73
203
  Relative urls will almost certainly not work if the mailer fails validation. Solution? Only use absolute urls.
74
204
 
205
+ Unless you set up the field validations in the "mailer" part, validation will be implemented via easily spoofable HTML attributes. Think of them of more like guidelines in that case.
75
206
 
76
207
  == History
77
208
 
@@ -91,5 +222,4 @@ Seriously restructured by: Nathaniel Talbott - terralien.com
91
222
 
92
223
  == Todo
93
224
 
94
- * Support for file attachments to emails
95
225
  * Tests
@@ -8,7 +8,6 @@ class MailController < ApplicationController
8
8
  @page.request, @page.response = request, response
9
9
 
10
10
  config, part_page = config_and_page(@page)
11
-
12
11
  mail = Mail.new(part_page, config, params[:mailer])
13
12
  @page.last_mail = part_page.last_mail = mail
14
13
  process_mail(mail, config)
@@ -34,4 +33,4 @@ class MailController < ApplicationController
34
33
  [(string.empty? ? {} : YAML::load(string).symbolize_keys), page]
35
34
  end
36
35
 
37
- end
36
+ end
data/app/models/mail.rb CHANGED
@@ -1,15 +1,32 @@
1
1
  class Mail
2
- attr_reader :page, :config, :data, :errors
2
+ attr_reader :page, :config, :data, :leave_blank, :disallow_links, :errors
3
+
3
4
  def initialize(page, config, data)
4
5
  @page, @config, @data = page, config.with_indifferent_access, data
5
- @required = @data.delete(:required)
6
+ @required = required_fields
7
+ @leave_blank = leave_blank_field
8
+ @disallow_links = disallow_link_fields
6
9
  @errors = {}
7
10
  end
8
11
 
9
12
  def self.valid_config?(config)
10
- return false if config['recipients'].blank? and config['recipients_field'].blank?
11
- return false if config['from'].blank? and config['from_field'].blank?
12
- true
13
+ config_errors(config).empty?
14
+ end
15
+
16
+ def self.config_errors(config)
17
+ config_errors = {}
18
+ %w(recipients from).each do |required_field|
19
+ if config[required_field].blank? and config["#{required_field}_field"].blank?
20
+ config_errors[required_field] = "is required"
21
+ end
22
+ end
23
+ config_errors
24
+ end
25
+
26
+ def self.config_error_messages(config)
27
+ config_errors(config).collect do |field, message|
28
+ "'#{field}' #{message}"
29
+ end.to_sentence
13
30
  end
14
31
 
15
32
  def valid?
@@ -37,16 +54,47 @@ class Mail
37
54
 
38
55
  if @required
39
56
  @required.each do |name, msg|
40
- if data[name].blank?
41
- errors[name] = ((msg.blank? || %w(1 true required).include?(msg)) ? "is required." : msg)
57
+ if "as_email" == msg
58
+ unless valid_email?(data[name])
59
+ errors[name] = "invalid email address."
60
+ @valid = false
61
+ end
62
+ elsif m = msg.match(/\/(.*)\//)
63
+ regex = Regexp.new(m[1])
64
+ unless data[name] =~ regex
65
+ errors[name] = "doesn't match regex (#{m[1]})"
66
+ @valid = false
67
+ end
68
+ else
69
+ if data[name].blank?
70
+ errors[name] = ((msg.blank? || %w(1 true required not_blank).include?(msg)) ? "is required." : msg)
71
+ @valid = false
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ if @disallow_links.present?
78
+ pattern = /www|&amp;|http:|mailto:|bcc:|href|cc:|multipart|\[url|Content-Type:/i
79
+ @disallow_links.each do |field|
80
+ if @data[field] =~ pattern
81
+ errors[field] = %q(must not contain the following text: "www", "&amp;amp;", "http:", "mailto:", "bcc:", "href", "multipart", "[url", or "Content-Type:")
42
82
  @valid = false
43
83
  end
44
84
  end
85
+ end
86
+
87
+ if @leave_blank.present?
88
+ unless @data[@leave_blank] == ''
89
+ errors[@leave_blank] = "must be left blank."
90
+ @valid = false
91
+ end
45
92
  end
46
93
  end
94
+
47
95
  @valid
48
96
  end
49
-
97
+
50
98
  def from
51
99
  config[:from] || data[config[:from_field]]
52
100
  end
@@ -71,14 +119,33 @@ class Mail
71
119
  data[config[:cc_field]] || config[:cc] || ""
72
120
  end
73
121
 
122
+ def files
123
+ res = []
124
+ data.each_value do |d|
125
+ res << d if StringIO === d or Tempfile === d
126
+ end
127
+ res
128
+ end
129
+
130
+ def filesize_limit
131
+ config[:filesize_limit] || 0
132
+ end
133
+
134
+ def plain_body
135
+ return nil if not valid?
136
+ @plain_body ||= (page.part( :email ) ? page.render_part( :email ) : page.render_part( :email_plain ))
137
+ end
138
+
139
+ def html_body
140
+ return nil if not valid?
141
+ @html_body = page.render_part( :email_html ) || nil
142
+ end
143
+
74
144
  def send
75
145
  return false if not valid?
76
146
 
77
- plain_body = (page.part( :email ) ? page.render_part( :email ) : page.render_part( :email_plain ))
78
- html_body = page.render_part( :email_html ) || nil
79
-
80
147
  if plain_body.blank? and html_body.blank?
81
- plain_body = <<-EMAIL
148
+ @plain_body = <<-EMAIL
82
149
  The following information was posted:
83
150
  #{data.to_hash.to_yaml}
84
151
  EMAIL
@@ -94,10 +161,12 @@ The following information was posted:
94
161
  :recipients => recipients,
95
162
  :from => from,
96
163
  :subject => subject,
97
- :plain_body => plain_body,
98
- :html_body => html_body,
164
+ :plain_body => @plain_body,
165
+ :html_body => @html_body,
99
166
  :cc => cc,
100
- :headers => headers
167
+ :headers => headers,
168
+ :files => files,
169
+ :filesize_limit => filesize_limit
101
170
  )
102
171
  @sent = true
103
172
  rescue Exception => e
@@ -112,10 +181,22 @@ The following information was posted:
112
181
  protected
113
182
 
114
183
  def valid_email?(email)
115
- (email.blank? ? true : email =~ /.@.+\../)
184
+ (email.blank? ? false : email =~ /^[^@]+@([^@.]+\.)[^@]+$/)
116
185
  end
117
186
 
118
187
  def is_required_field?(field_name)
119
188
  @required && @required.any? {|name,_| name == field_name}
120
189
  end
190
+
191
+ def required_fields
192
+ @config.has_key?(:required) ? @config[:required] : @data.delete(:required)
193
+ end
194
+
195
+ def leave_blank_field
196
+ @config[:leave_blank] if @config.has_key?(:leave_blank)
197
+ end
198
+
199
+ def disallow_link_fields
200
+ @config[:disallow_links] if @config.has_key?(:disallow_links)
201
+ end
121
202
  end
data/app/models/mailer.rb CHANGED
@@ -1,19 +1,32 @@
1
1
  class Mailer < ActionMailer::Base
2
2
  def generic_mail(options)
3
- @recipients = options[:recipients]
4
- @from = options[:from] || ""
5
- @cc = options[:cc] || ""
6
- @bcc = options[:bcc] || ""
7
- @subject = options[:subject] || ""
8
- @headers = options[:headers] || {}
9
- # Not sure that charset works, can see no effect in tests
10
- @charset = options[:charset] || "utf-8"
11
- @content_type = "multipart/alternative"
3
+ recipients options[:recipients]
4
+ from options[:from] || ""
5
+ cc options[:cc] || ""
6
+ bcc options[:bcc] || ""
7
+ subject options[:subject] || ""
8
+ headers options[:headers] || {}
9
+ content_type "multipart/mixed"
10
+ charset options[:charset] || "utf-8"
11
+
12
12
  if options.has_key? :plain_body
13
13
  part :content_type => "text/plain", :body => (options[:plain_body] || "")
14
14
  end
15
15
  if options.has_key? :html_body and !options[:html_body].blank?
16
16
  part :content_type => "text/html", :body => (options[:html_body] || "")
17
17
  end
18
+ # attchments
19
+ files = options[:files] || []
20
+ limit = options[:filesize_limit] || 0
21
+ files.each do |f|
22
+ if(limit == 0 || f.size <= limit)
23
+ attachment(
24
+ :content_type => "application/octet-stream",
25
+ :body => f.read,
26
+ :filename => f.original_filename)
27
+ else
28
+ raise "The file #{f.original_filename} is too large. The maximum size allowed is #{limit.to_s} bytes."
29
+ end
30
+ end
18
31
  end
19
32
  end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.resources :mail, :path_prefix => "/pages/:page_id", :controller => "mail"
3
+ end
@@ -0,0 +1,10 @@
1
+ class RevertMailerPageClassToPage < ActiveRecord::Migration
2
+ def self.up
3
+ # Leaving pages with MailerPage class_name if this model no longer exists would result in an error
4
+ Page.update_all("class_name = 'Page'", "class_name = 'MailerPage'")
5
+ end
6
+
7
+ def self.down
8
+ # Can not be reverted!
9
+ end
10
+ end
@@ -0,0 +1,37 @@
1
+ class MailerPageDataset < Dataset::Base
2
+ uses :pages
3
+
4
+ def load
5
+ create_page "Contact" do
6
+ create_page_part "contact_body",
7
+ :name => "body",
8
+ :content => %Q{
9
+ <r:mailer:form>
10
+ Name:<br/>
11
+ <r:mailer:text name="name" /> <br/>
12
+ Email:<br/>
13
+ <r:mailer:text name="email" /> <br/>
14
+ Message:<br/>
15
+ <r:mailer:textarea name="message" /> <br/>
16
+ File:<br/>
17
+ <r:mailer:file name="attached_file" /> <br/>
18
+ <input type="submit" value="Send" />
19
+ </r:mailer:form>}
20
+ create_page_part "mailer",
21
+ :content => {
22
+ 'subject' => 'From the website of Whatever',
23
+ 'from' => 'no_reply@aissac.ro',
24
+ 'redirect_to' => '/contact/thank-you',
25
+ 'recipients' => 'example@aissac.ro'}.to_yaml
26
+ create_page_part "email",
27
+ :content => %Q{
28
+ <r:mailer>
29
+ Name: <r:get name="name" />
30
+ Email: <r:get name="email" />
31
+ Message: <r:get name="message" />
32
+ </r:mailer>
33
+ }
34
+ create_page "Thank You", :body => "Thank you!"
35
+ end
36
+ end
37
+ end