radiant-comments-extension 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/.gitignore +3 -0
  2. data/CHANGELOG +40 -0
  3. data/HELP_admin.markdown +52 -0
  4. data/HELP_designer.markdown +36 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +53 -0
  7. data/Rakefile +133 -0
  8. data/TODO +6 -0
  9. data/VERSION +1 -0
  10. data/app/controllers/admin/comments_controller.rb +130 -0
  11. data/app/controllers/comments_controller.rb +59 -0
  12. data/app/helpers/admin/comments_helper.rb +7 -0
  13. data/app/models/akismet_spam_filter.rb +37 -0
  14. data/app/models/comment.rb +121 -0
  15. data/app/models/comment_mailer.rb +24 -0
  16. data/app/models/mollom_spam_filter.rb +52 -0
  17. data/app/models/simple_spam_filter.rb +38 -0
  18. data/app/models/spam_filter.rb +43 -0
  19. data/app/views/admin/comments/_comment.rhtml +34 -0
  20. data/app/views/admin/comments/_form.rhtml +36 -0
  21. data/app/views/admin/comments/edit.rhtml +5 -0
  22. data/app/views/admin/comments/index.rhtml +55 -0
  23. data/app/views/admin/pages/_comments.rhtml +0 -0
  24. data/app/views/admin/pages/_edit_comments_enabled.rhtml +8 -0
  25. data/app/views/admin/pages/_index_head_view_comments.rhtml +1 -0
  26. data/app/views/admin/pages/_index_view_comments.rhtml +11 -0
  27. data/app/views/comment_mailer/comment_notification.rhtml +21 -0
  28. data/app/views/comments/_comment.rhtml +1 -0
  29. data/app/views/comments/_form.rhtml +23 -0
  30. data/app/views/comments/_new.rhtml +5 -0
  31. data/autotest/discover.rb +3 -0
  32. data/comments_extension.rb +81 -0
  33. data/cucumber.yml +1 -0
  34. data/db/migrate/001_create_comments.rb +29 -0
  35. data/db/migrate/002_create_snippets.rb +115 -0
  36. data/db/migrate/003_change_filter_id_from_integer_to_string.rb +10 -0
  37. data/db/migrate/004_add_approval_columns.rb +13 -0
  38. data/db/migrate/005_add_mollomid_column.rb +11 -0
  39. data/db/migrate/006_move_config_to_migrations.rb +22 -0
  40. data/db/migrate/007_add_preference_for_simple_spamcheck.rb +12 -0
  41. data/features/support/env.rb +16 -0
  42. data/features/support/paths.rb +16 -0
  43. data/lib/akismet.rb +134 -0
  44. data/lib/comment_page_extensions.rb +41 -0
  45. data/lib/comment_tags.rb +338 -0
  46. data/lib/mollom.rb +246 -0
  47. data/lib/radiant-comments-extension.rb +0 -0
  48. data/lib/tasks/comments_extension_tasks.rake +68 -0
  49. data/public/images/admin/accept.png +0 -0
  50. data/public/images/admin/comment_edit.png +0 -0
  51. data/public/images/admin/comments.png +0 -0
  52. data/public/images/admin/comments_delete.png +0 -0
  53. data/public/images/admin/delete.png +0 -0
  54. data/public/images/admin/email.png +0 -0
  55. data/public/images/admin/error.png +0 -0
  56. data/public/images/admin/link.png +0 -0
  57. data/public/images/admin/page_white_edit.png +0 -0
  58. data/public/images/admin/table_save.png +0 -0
  59. data/public/images/admin/tick.png +0 -0
  60. data/public/stylesheets/admin/comments.css +41 -0
  61. data/radiant-comments-extension.gemspec +133 -0
  62. data/spec/controllers/admin/comments_controller_spec.rb +57 -0
  63. data/spec/controllers/admin/comments_routing_spec.rb +43 -0
  64. data/spec/controllers/page_postback_spec.rb +51 -0
  65. data/spec/datasets/comments_dataset.rb +7 -0
  66. data/spec/models/akismet_spam_filter_spec.rb +61 -0
  67. data/spec/models/comment_spec.rb +148 -0
  68. data/spec/models/comment_tags_spec.rb +55 -0
  69. data/spec/models/mollom_spam_filter_spec.rb +103 -0
  70. data/spec/models/simple_spam_filter_spec.rb +44 -0
  71. data/spec/models/spam_filter_spec.rb +38 -0
  72. data/spec/spec.opts +6 -0
  73. data/spec/spec_helper.rb +36 -0
  74. data/test/fixtures/users.yml +6 -0
  75. data/test/integration/comment_enabling_test.rb +18 -0
  76. data/test/test_helper.rb +24 -0
  77. data/test/unit/comment_test.rb +52 -0
  78. metadata +177 -0
@@ -0,0 +1,12 @@
1
+ class AddPreferenceForSimpleSpamcheck < ActiveRecord::Migration
2
+ def self.up
3
+ if Radiant::Config['comments.simple_spam_filter_required?'].blank?
4
+ Radiant::Config.create(:key => 'comments.simple_spam_filter_required?', :value => true)
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ # not necessary
10
+ end
11
+ end
12
+
@@ -0,0 +1,16 @@
1
+ # Sets up the Rails environment for Cucumber
2
+ ENV["RAILS_ENV"] = "test"
3
+ # Extension root
4
+ extension_env = File.expand_path(File.dirname(__FILE__) + '/../../../../../config/environment')
5
+ require extension_env+'.rb'
6
+
7
+ Dir.glob(File.join(RADIANT_ROOT, "features", "**", "*.rb")).each {|step| require step}
8
+
9
+ Cucumber::Rails::World.class_eval do
10
+ include Dataset
11
+ datasets_directory "#{RADIANT_ROOT}/spec/datasets"
12
+ Dataset::Resolver.default = Dataset::DirectoryResolver.new("#{RADIANT_ROOT}/spec/datasets", File.dirname(__FILE__) + '/../../spec/datasets', File.dirname(__FILE__) + '/../datasets')
13
+ self.datasets_database_dump_path = "#{Rails.root}/tmp/dataset"
14
+
15
+ # dataset :comments
16
+ end
@@ -0,0 +1,16 @@
1
+ def path_to(page_name)
2
+ case page_name
3
+
4
+ when /the homepage/i
5
+ root_path
6
+
7
+ when /login/i
8
+ login_path
9
+
10
+ when /comments/i
11
+ admin_comments_path
12
+ # Add more page name => path mappings here
13
+ else
14
+ raise "Can't find mapping from \"#{page_name}\" to a path."
15
+ end
16
+ end
data/lib/akismet.rb ADDED
@@ -0,0 +1,134 @@
1
+ # Akismet
2
+ #
3
+ # Author:: David Czarnecki
4
+ # Copyright:: Copyright (c) 2005 - David Czarnecki
5
+ # License:: BSD
6
+ # Modified by Dieter Komendera, Sparkling Studios:
7
+ # append blog= to data string (Akismet said it is required)
8
+ # changed require 'net/HTTP' to require 'net/http' (to work for me unter GNU/Linux)
9
+ # Akismet Key - 4ebc7e35c512
10
+ class Akismet
11
+
12
+ require 'net/http'
13
+ require 'uri'
14
+
15
+ STANDARD_HEADERS = {
16
+ 'User-Agent' => 'Akismet Ruby API/1.0',
17
+ 'Content-Type' => 'application/x-www-form-urlencoded'
18
+ }
19
+
20
+ # Create a new instance of the Akismet class
21
+ #
22
+ # apiKey
23
+ # Your Akismet API key
24
+ # blog
25
+ # The blog associated with your api key
26
+ def initialize(apiKey, blog)
27
+ @apiKey = apiKey
28
+ @blog = blog
29
+ end
30
+
31
+ # Set proxy information
32
+ #
33
+ # proxyHost
34
+ # Hostname for the proxy to use
35
+ # proxyPort
36
+ # Port for the proxy
37
+ def setProxy(proxyHost, proxyPort)
38
+ @proxyPort = proxyPort
39
+ @proxyHost = proxyHost
40
+ end
41
+
42
+ def valid?
43
+ (!@apiKey.blank? && !@blog.blank? && verifyAPIKey)
44
+ end
45
+
46
+ # Call to check and verify your API key.
47
+ def verifyAPIKey
48
+ http = Net::HTTP.new('rest.akismet.com', 80, @proxyHost, @proxyPort)
49
+ path = '/1.1/verify-key'
50
+
51
+ data="key=#{@apiKey}&blog=#{@blog}"
52
+
53
+ resp, data = http.post(path, data, STANDARD_HEADERS)
54
+ (data == "valid")
55
+ end
56
+
57
+ # Internal call to Akismet. Prepares the data for posting to the Akismet service.
58
+ #
59
+ # akismet_function
60
+ # The Akismet function that should be called
61
+ # user_ip (required)
62
+ # IP address of the comment submitter.
63
+ # user_agent (required)
64
+ # User agent information.
65
+ # referrer (note spelling)
66
+ # The content of the HTTP_REFERER header should be sent here.
67
+ # permalink
68
+ # The permanent location of the entry the comment was submitted to.
69
+ # comment_type
70
+ # May be blank, comment, trackback, pingback, or a made up value like "registration".
71
+ # comment_author
72
+ # Submitted name with the comment
73
+ # comment_author_email
74
+ # Submitted email address
75
+ # comment_author_url
76
+ # Commenter URL.
77
+ # comment_content
78
+ # The content that was submitted.
79
+ # Other server enviroment variables
80
+ # In PHP there is an array of enviroment variables called $_SERVER which contains information about the web server itself as well as a key/value for every HTTP header sent with the request. This data is highly useful to Akismet as how the submited content interacts with the server can be very telling, so please include as much information as possible.
81
+ def callAkismet(akismet_function, user_ip, user_agent, referrer, permalink, comment_type, comment_author, comment_author_email, comment_author_url, comment_content, other)
82
+ http = Net::HTTP.new("#{@apiKey}.rest.akismet.com", 80, @proxyHost, @proxyPort)
83
+ path = "/1.1/#{akismet_function}"
84
+
85
+ data = "blog=#{@blog}&user_ip=#{user_ip}&user_agent=#{user_agent}&referrer=#{referrer}&permalink=#{permalink}&comment_type=#{comment_type}&comment_author=#{comment_author}&comment_author_email=#{comment_author_email}&comment_author_url=#{comment_author_url}&comment_content=#{comment_content}"
86
+ if (other != nil)
87
+ other.each_pair {|key, value| data.concat("&#{key}=#{value}")}
88
+ end
89
+
90
+ resp, data = http.post(path, data, STANDARD_HEADERS)
91
+
92
+ return (data != "false")
93
+ end
94
+
95
+ protected :callAkismet
96
+
97
+ # This is basically the core of everything. This call takes a number of arguments and characteristics about the submitted content and then returns a thumbs up or thumbs down. Almost everything is optional, but performance can drop dramatically if you exclude certain elements.
98
+ #
99
+ # user_ip (required)
100
+ # IP address of the comment submitter.
101
+ # user_agent (required)
102
+ # User agent information.
103
+ # referrer (note spelling)
104
+ # The content of the HTTP_REFERER header should be sent here.
105
+ # permalink
106
+ # The permanent location of the entry the comment was submitted to.
107
+ # comment_type
108
+ # May be blank, comment, trackback, pingback, or a made up value like "registration".
109
+ # comment_author
110
+ # Submitted name with the comment
111
+ # comment_author_email
112
+ # Submitted email address
113
+ # comment_author_url
114
+ # Commenter URL.
115
+ # comment_content
116
+ # The content that was submitted.
117
+ # Other server enviroment variables
118
+ # In PHP there is an array of enviroment variables called $_SERVER which contains information about the web server itself as well as a key/value for every HTTP header sent with the request. This data is highly useful to Akismet as how the submited content interacts with the server can be very telling, so please include as much information as possible.
119
+ def commentCheck(user_ip, user_agent, referrer, permalink, comment_type, comment_author, comment_author_email, comment_author_url, comment_content, other)
120
+ return callAkismet('comment-check', user_ip, user_agent, referrer, permalink, comment_type, comment_author, comment_author_email, comment_author_url, comment_content, other)
121
+ end
122
+
123
+ # This call is for submitting comments that weren't marked as spam but should have been. It takes identical arguments as comment check.
124
+ # The call parameters are the same as for the #commentCheck method.
125
+ def submitSpam(user_ip, user_agent, referrer, permalink, comment_type, comment_author, comment_author_email, comment_author_url, comment_content, other)
126
+ callAkismet('submit-spam', user_ip, user_agent, referrer, permalink, comment_type, comment_author, comment_author_email, comment_author_url, comment_content, other)
127
+ end
128
+
129
+ # This call is intended for the marking of false positives, things that were incorrectly marked as spam. It takes identical arguments as comment check and submit spam.
130
+ # The call parameters are the same as for the #commentCheck method.
131
+ def submitHam(user_ip, user_agent, referrer, permalink, comment_type, comment_author, comment_author_email, comment_author_url, comment_content, other)
132
+ callAkismet('submit-ham', user_ip, user_agent, referrer, permalink, comment_type, comment_author, comment_author_email, comment_author_url, comment_content, other)
133
+ end
134
+ end
@@ -0,0 +1,41 @@
1
+ module CommentPageExtensions
2
+ def self.included(base)
3
+ base.class_eval do
4
+ alias_method_chain :process, :comments
5
+
6
+ has_many :comments, :dependent => :delete_all, :order => "created_at ASC"
7
+ attr_accessor :last_comment
8
+ attr_accessor :selected_comment
9
+ end
10
+ end
11
+
12
+ def has_visible_comments?
13
+ !(comments.approved.empty? && selected_comment.nil?)
14
+ end
15
+
16
+ def process_with_comments(request, response)
17
+ if Radiant::Config['comments.post_to_page?'] && request.post? && request.parameters[:comment]
18
+ begin
19
+ comment = self.comments.build(request.parameters[:comment])
20
+ comment.request = self.request = request
21
+ comment.save!
22
+
23
+ if Radiant::Config['comments.notification'] == "true"
24
+ if comment.approved? || Radiant::Config['comments.notify_unapproved'] == "true"
25
+ CommentMailer.deliver_comment_notification(comment)
26
+ end
27
+ end
28
+ if comment.approved?
29
+ absolute_url = "#{request.protocol}#{request.host_with_port}#{relative_url_for(url, request)}#comment-#{comment.id}"
30
+ response.redirect(absolute_url, 303)
31
+ return
32
+ else
33
+ self.selected_comment = comment
34
+ end
35
+ rescue ActiveRecord::RecordInvalid
36
+ self.last_comment = comment
37
+ end
38
+ end
39
+ process_without_comments(request, response)
40
+ end
41
+ end
@@ -0,0 +1,338 @@
1
+ module CommentTags
2
+ include Radiant::Taggable
3
+
4
+ desc "Provides tags and behaviors to support comments in Radiant."
5
+
6
+ desc %{
7
+ Renders the contained elements if comments are enabled on the page.
8
+ }
9
+ tag "if_enable_comments" do |tag|
10
+ tag.expand if (tag.locals.page.enable_comments?)
11
+ end
12
+ # makes more sense to me
13
+ tag "if_comments_enabled" do |tag|
14
+ tag.expand if (tag.locals.page.enable_comments?)
15
+ end
16
+
17
+ desc %{
18
+ Renders the contained elements unless comments are enabled on the page.
19
+ }
20
+ tag "unless_enable_comments" do |tag|
21
+ tag.expand unless (tag.locals.page.enable_comments?)
22
+ end
23
+
24
+ # makes more sense to me
25
+ tag "unless_comments_enabled" do |tag|
26
+ tag.expand unless (tag.locals.page.enable_comments?)
27
+ end
28
+
29
+ desc %{
30
+ Renders the contained elements if the page has comments.
31
+ }
32
+ tag "if_comments" do |tag|
33
+ tag.expand if tag.locals.page.has_visible_comments?
34
+ end
35
+
36
+ desc %{
37
+ Renders the contained elements unless the page has comments.
38
+ }
39
+ tag "unless_comments" do |tag|
40
+ tag.expand unless tag.locals.page.has_visible_comments?
41
+ end
42
+
43
+ desc %{
44
+ Renders the contained elements if the page has comments _or_ comment is enabled on it.
45
+ }
46
+ tag "if_comments_or_enable_comments" do |tag|
47
+ tag.expand if(tag.locals.page.has_visible_comments? || tag.locals.page.enable_comments?)
48
+ end
49
+
50
+ desc %{
51
+ Gives access to comment-related tags
52
+ }
53
+ tag "comments" do |tag|
54
+ comments = tag.locals.page.comments.approved
55
+ tag.expand
56
+ end
57
+
58
+ desc %{
59
+ Cycles through each comment and renders the enclosed tags for each.
60
+ }
61
+ tag "comments:each" do |tag|
62
+ page = tag.locals.page
63
+ comments = page.comments.approved.to_a
64
+ comments << page.selected_comment if page.selected_comment && page.selected_comment.unapproved?
65
+ result = []
66
+ comments.each_with_index do |comment, index|
67
+ tag.locals.comment = comment
68
+ tag.locals.index = index
69
+ result << tag.expand
70
+ end
71
+ result
72
+ end
73
+
74
+ desc %{
75
+ Gives access to the particular fields for each comment.
76
+ }
77
+ tag "comments:field" do |tag|
78
+ tag.expand
79
+ end
80
+
81
+ desc %{
82
+ Renders the index number for this comment.
83
+ }
84
+ tag 'comments:field:index' do |tag|
85
+ tag.locals.index + 1
86
+ end
87
+
88
+ %w(id author author_email author_url content content_html filter_id).each do |field|
89
+ desc %{ Print the value of the #{field} field for this comment. }
90
+ tag "comments:field:#{field}" do |tag|
91
+ options = tag.attr.dup
92
+ #options.inspect
93
+ value = tag.locals.comment.send(field)
94
+ value
95
+ end
96
+ end
97
+
98
+ desc %{
99
+ Renders the date a comment was created.
100
+
101
+ *Usage:*
102
+ <pre><code><r:date [format="%A, %B %d, %Y"] /></code></pre>
103
+ }
104
+ tag 'comments:field:date' do |tag|
105
+ comment = tag.locals.comment
106
+ format = (tag.attr['format'] || '%A, %B %d, %Y')
107
+ date = comment.created_at
108
+ date.strftime(format)
109
+ end
110
+
111
+ desc %{
112
+ Renders a link if there's an author_url, otherwise just the author's name.
113
+ }
114
+ tag "comments:field:author_link" do |tag|
115
+ if tag.locals.comment.author_url.blank?
116
+ tag.locals.comment.author
117
+ else
118
+ %(<a href="#{tag.locals.comment.author_url}">#{tag.locals.comment.author}</a>)
119
+ end
120
+ end
121
+
122
+ desc %{
123
+ Renders the contained elements if the comment has an author_url specified.
124
+ }
125
+ tag "comments:field:if_author_url" do |tag|
126
+ tag.expand unless tag.locals.comment.author_url.blank?
127
+ end
128
+
129
+ desc %{
130
+ Renders the contained elements if the comment is selected - that is, if it is a comment
131
+ the user has just posted
132
+ }
133
+ tag "comments:field:if_selected" do |tag|
134
+ tag.expand if tag.locals.comment == tag.locals.page.selected_comment
135
+ end
136
+
137
+ desc %{
138
+ Renders the contained elements if the comment has been approved
139
+ }
140
+ tag "comments:field:if_approved" do |tag|
141
+ tag.expand if tag.locals.comment.approved?
142
+ end
143
+
144
+ desc %{
145
+ Renders the contained elements if the comment has not been approved
146
+ }
147
+ tag "comments:field:unless_approved" do |tag|
148
+ tag.expand unless tag.locals.comment.approved?
149
+ end
150
+
151
+ desc %{
152
+ Renders a Gravatar URL for the author of the comment.
153
+ }
154
+ tag "comments:field:gravatar_url" do |tag|
155
+ email = tag.locals.comment.author_email
156
+ size = tag.attr['size']
157
+ format = tag.attr['format']
158
+ rating = tag.attr['rating']
159
+ default = tag.attr['default']
160
+ md5 = Digest::MD5.hexdigest(email)
161
+ returning "http://www.gravatar.com/avatar/#{md5}" do |url|
162
+ url << ".#{format.downcase}" if format
163
+ if size || rating || default
164
+ attrs = []
165
+ attrs << "s=#{size}" if size
166
+ attrs << "d=#{default}" if default
167
+ attrs << "r=#{rating.downcase}" if rating
168
+ url << "?#{attrs.join('&')}"
169
+ end
170
+ end
171
+ end
172
+
173
+ desc %{
174
+ Renders a comment form.
175
+
176
+ *Usage:*
177
+ <r:comment:form [class="comments" id="comment_form"]>...</r:comment:form>
178
+ }
179
+ tag "comments:form" do |tag|
180
+ attrs = tag.attr.symbolize_keys
181
+ html_class, html_id = attrs[:class], attrs[:id]
182
+ r = %Q{ <form action="#{tag.locals.page.url}#{'comments' unless Radiant::Config['comments.post_to_page?']}}
183
+ r << %Q{##{html_id}} unless html_id.blank?
184
+ r << %{" method="post" } #comlpete the quotes for the action
185
+ r << %{ id="#{html_id}" } unless html_id.blank?
186
+ r << %{ class="#{html_class}" } unless html_class.blank?
187
+ r << '>' #close the form element
188
+ r << tag.expand
189
+ r << %{</form>}
190
+ r
191
+ end
192
+
193
+ tag 'comments:error' do |tag|
194
+ if comment = tag.locals.page.last_comment
195
+ if on = tag.attr['on']
196
+ if error = comment.errors.on(on)
197
+ tag.locals.error_message = error
198
+ tag.expand
199
+ end
200
+ else
201
+ tag.expand if !comment.valid?
202
+ end
203
+ end
204
+ end
205
+
206
+ tag 'comments:error:message' do |tag|
207
+ tag.locals.error_message
208
+ end
209
+
210
+ %w(text password hidden).each do |type|
211
+ desc %{Builds a #{type} form field for comments.}
212
+ tag "comments:#{type}_field_tag" do |tag|
213
+ attrs = tag.attr.symbolize_keys
214
+ r = %{<input type="#{type}"}
215
+ r << %{ id="comment_#{attrs[:name]}"}
216
+ r << %{ name="comment[#{attrs[:name]}]"}
217
+ r << %{ class="#{attrs[:class]}"} if attrs[:class]
218
+ if value = (tag.locals.page.last_comment ? tag.locals.page.last_comment.send(attrs[:name]) : attrs[:value])
219
+ r << %{ value="#{value}" }
220
+ end
221
+ r << %{ />}
222
+ end
223
+ end
224
+
225
+ %w(submit reset).each do |type|
226
+ desc %{Builds a #{type} form button for comments.}
227
+ tag "comments:#{type}_tag" do |tag|
228
+ attrs = tag.attr.symbolize_keys
229
+ r = %{<input type="#{type}"}
230
+ r << %{ id="#{attrs[:name]}"}
231
+ r << %{ name="#{attrs[:name]}"}
232
+ r << %{ class="#{attrs[:class]}"} if attrs[:class]
233
+ r << %{ value="#{attrs[:value]}" } if attrs[:value]
234
+ r << %{ />}
235
+ end
236
+ end
237
+
238
+ desc %{Builds a text_area form field for comments.}
239
+ tag "comments:text_area_tag" do |tag|
240
+ attrs = tag.attr.symbolize_keys
241
+ r = %{<textarea}
242
+ r << %{ id="comment_#{attrs[:name]}"}
243
+ r << %{ name="comment[#{attrs[:name]}]"}
244
+ r << %{ class="#{attrs[:class]}"} if attrs[:class]
245
+ r << %{ rows="#{attrs[:rows]}"} if attrs[:rows]
246
+ r << %{ cols="#{attrs[:cols]}"} if attrs[:cols]
247
+ r << %{>}
248
+ if content = (tag.locals.page.last_comment ? tag.locals.page.last_comment.send(attrs[:name]) : attrs[:content])
249
+ r << content
250
+ end
251
+ r << %{</textarea>}
252
+ end
253
+
254
+ desc %{Build a drop_box form field for the filters avaiable.}
255
+ tag "comments:filter_box_tag" do |tag|
256
+ attrs = tag.attr.symbolize_keys
257
+ value = attrs.delete(:value)
258
+ name = attrs.delete(:name)
259
+ r = %{<select name="comment[#{name}]"}
260
+ unless attrs.empty?
261
+ r << " "
262
+ r << attrs.map {|k,v| %Q(#{k}="#{v}") }.join(" ")
263
+ end
264
+ r << %{>}
265
+
266
+ TextFilter.descendants.each do |filter|
267
+
268
+ r << %{<option value="#{filter.filter_name}"}
269
+ r << %{ selected="selected"} if value == filter.filter_name
270
+ r << %{>#{filter.filter_name}</option>}
271
+
272
+ end
273
+
274
+ r << %{</select>}
275
+ end
276
+
277
+ desc %{Prints the number of comments. }
278
+ tag "comments:count" do |tag|
279
+ tag.locals.page.comments.approved.count
280
+ end
281
+
282
+
283
+ tag "recent_comments" do |tag|
284
+ tag.expand
285
+ end
286
+
287
+ desc %{Returns the last [limit] comments throughout the site.
288
+
289
+ *Usage:*
290
+ <pre><code><r:recent_comments:each [limit="10"]>...</r:recent_comments:each></code></pre>
291
+ }
292
+ tag "recent_comments:each" do |tag|
293
+ limit = tag.attr['limit'] || 10
294
+ comments = Comment.approved.recent.all(:limit => limit)
295
+ result = []
296
+ comments.each_with_index do |comment, index|
297
+ tag.locals.comment = comment
298
+ tag.locals.index = index
299
+ tag.locals.page = comment.page
300
+ result << tag.expand
301
+ end
302
+ result
303
+ end
304
+
305
+ desc %{
306
+ Use this to prevent spam bots from filling your site with spam.
307
+
308
+ *Usage:*
309
+ <pre><code>What day comes after Monday? <r:comments:spam_answer_tag answer="Tuesday" /></code></pre>
310
+ }
311
+ tag "comments:spam_answer_tag" do |tag|
312
+ attrs = tag.attr.symbolize_keys
313
+ valid_spam_answer = attrs[:answer] || 'hemidemisemiquaver'
314
+ md5_answer = Digest::MD5.hexdigest(valid_spam_answer.to_slug)
315
+ r = %{<input type="text" id="comment_spam_answer" name="comment[spam_answer]"}
316
+ r << %{ class="#{attrs[:class]}"} if attrs[:class]
317
+ if value = (tag.locals.page.last_comment ? tag.locals.page.last_comment.send(:spam_answer) : '')
318
+ r << %{ value="#{value}" }
319
+ end
320
+ r << %{ />}
321
+ r << %{<input type="hidden" name="comment[valid_spam_answer]" value="#{md5_answer}" />}
322
+ end
323
+
324
+ desc %{
325
+ Render the contained elements if using the simple spam filter.
326
+ }
327
+ tag "if_comments_simple_spam_filter_enabled" do |tag|
328
+ tag.expand if Comment.simple_spam_filter_enabled?
329
+ end
330
+
331
+ desc %{
332
+ Render the contained elements unless using the simple spam filter.
333
+ }
334
+ tag "unless_comments_simple_spam_filter_enabled" do |tag|
335
+ tag.expand unless Comment.simple_spam_filter_enabled?
336
+ end
337
+
338
+ end