typo 4.0.0 → 4.0.1

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.
Files changed (82) hide show
  1. data/app/controllers/admin/comments_controller.rb +1 -1
  2. data/app/controllers/admin/content_controller.rb +1 -3
  3. data/app/controllers/admin/feedback_controller.rb +36 -31
  4. data/app/controllers/admin/sidebar_controller.rb +13 -2
  5. data/app/controllers/admin/users_controller.rb +2 -1
  6. data/app/controllers/articles_controller.rb +10 -19
  7. data/app/controllers/xml_controller.rb +2 -2
  8. data/app/helpers/admin/base_helper.rb +7 -3
  9. data/app/helpers/application_helper.rb +2 -2
  10. data/app/helpers/articles_helper.rb +5 -4
  11. data/app/models/article.rb +16 -10
  12. data/app/models/blog.rb +4 -10
  13. data/app/models/comment.rb +17 -36
  14. data/app/models/content.rb +31 -53
  15. data/app/models/content_state/base.rb +46 -3
  16. data/app/models/content_state/draft.rb +2 -9
  17. data/app/models/content_state/ham.rb +31 -0
  18. data/app/models/content_state/just_marked_as_ham.rb +10 -0
  19. data/app/models/content_state/just_marked_as_spam.rb +23 -0
  20. data/app/models/content_state/just_presumed_ham.rb +37 -0
  21. data/app/models/content_state/just_published.rb +15 -8
  22. data/app/models/content_state/new.rb +3 -10
  23. data/app/models/content_state/presumed_ham.rb +27 -0
  24. data/app/models/content_state/presumed_spam.rb +31 -0
  25. data/app/models/content_state/publication_pending.rb +7 -9
  26. data/app/models/content_state/published.rb +10 -9
  27. data/app/models/content_state/spam.rb +23 -0
  28. data/app/models/content_state/unclassified.rb +29 -0
  29. data/app/models/content_state/withdrawn.rb +28 -0
  30. data/app/models/email_notifier.rb +0 -1
  31. data/app/models/feedback.rb +151 -0
  32. data/app/models/trackback.rb +22 -29
  33. data/app/views/admin/feedback/_item.rhtml +5 -5
  34. data/app/views/admin/feedback/list.rhtml +13 -11
  35. data/app/views/admin/general/index.rhtml +8 -4
  36. data/app/views/admin/users/show.rhtml +7 -1
  37. data/app/views/articles/read.rhtml +2 -2
  38. data/bin/typo +7 -6
  39. data/components/plugins/sidebars/recent_comments_controller.rb +1 -1
  40. data/config/environment.rb +2 -0
  41. data/db/migrate/046_fixup_forthcoming_publications.rb +1 -1
  42. data/db/migrate/048_remove_count_caching.rb +31 -0
  43. data/db/migrate/049_move_feedback_to_new_state_machine.rb +33 -0
  44. data/db/schema.mysql-v3.sql +3 -4
  45. data/db/schema.mysql.sql +3 -4
  46. data/db/schema.postgresql.sql +3 -4
  47. data/db/schema.rb +12 -17
  48. data/db/schema.sqlite.sql +3 -4
  49. data/db/schema.sqlserver.sql +3 -4
  50. data/db/schema_version +1 -1
  51. data/doc/Installer.txt +4 -0
  52. data/lib/jabber_notify.rb +6 -2
  53. data/lib/sidebars/plugin.rb +2 -1
  54. data/lib/tasks/release.rake +5 -4
  55. data/lib/typo_version.rb +1 -1
  56. data/public/stylesheets/administration.css +22 -2
  57. data/test/fixtures/blogs.yml +2 -0
  58. data/test/fixtures/contents.yml +7 -7
  59. data/test/functional/admin/users_controller_test.rb +3 -0
  60. data/test/functional/articles_controller_test.rb +16 -1
  61. data/test/mocks/test/xmlrpc_mock.rb +5 -4
  62. data/test/unit/article_test.rb +16 -4
  63. data/test/unit/comment_test.rb +57 -35
  64. data/test/unit/content_state/factory_test.rb +7 -6
  65. data/test/unit/ping_test.rb +14 -0
  66. data/test/unit/trackback_test.rb +16 -15
  67. metadata +26 -26
  68. data/config/database.yml-pgsql +0 -17
  69. data/config/database.yml.sqlite +0 -14
  70. data/config/mail.yml +0 -8
  71. data/config/mongrel.conf +0 -2
  72. data/db/converters/mt-import.rb +0 -72
  73. data/db/development_structure.sql +0 -691
  74. data/installer/rails-installer.rb +0 -527
  75. data/installer/rails-installer/commands.rb +0 -118
  76. data/installer/rails-installer/web-servers.rb +0 -110
  77. data/log/development.log-1 +0 -991
  78. data/log/development.log-2 +0 -422
  79. data/log/development.log-3 +0 -429
  80. data/log/development.log-4 +0 -174
  81. data/svk-commitP6cVv.tmp +0 -1
  82. data/vendor/ruby-mp3info/lib/mp3info.rb +0 -720
@@ -0,0 +1,31 @@
1
+ module ContentState
2
+ class PresumedSpam < Base
3
+ include Reloadable
4
+ include Singleton
5
+
6
+ def enter_hook(content)
7
+ super
8
+ content[:published] = false
9
+ end
10
+
11
+ def is_spam?(content)
12
+ true
13
+ end
14
+
15
+ def mark_as_ham(content)
16
+ content.state = Factory.new(:just_marked_as_ham)
17
+ end
18
+
19
+ def withdraw(content)
20
+ content.mark_as_spam
21
+ end
22
+
23
+ def confirm_classification(content)
24
+ content.mark_as_spam
25
+ end
26
+
27
+ def to_s
28
+ "Spam?"
29
+ end
30
+ end
31
+ end
@@ -2,17 +2,10 @@ module ContentState
2
2
  class PublicationPending < Base
3
3
  include Reloadable
4
4
  include Singleton
5
- class << self
6
- def derivable_from(content)
7
- !content.published && content.published_at ||
8
- content.new_record? && content.published_at &&
9
- content.published_at > Time.now
10
- end
11
- end
12
5
 
13
- def serialize_on(content)
6
+ def enter_hook(content)
7
+ super
14
8
  content[:published] = false if content.new_record?
15
- true
16
9
  end
17
10
 
18
11
  def change_published_state(content, boolean)
@@ -39,5 +32,10 @@ module ContentState
39
32
  def post_trigger(content)
40
33
  Trigger.post_action(content.published_at, content, 'publish!')
41
34
  end
35
+
36
+ def withdraw(content)
37
+ content[:published_at] = nil
38
+ content.state = Draft.instance
39
+ end
42
40
  end
43
41
  end
@@ -2,28 +2,29 @@ module ContentState
2
2
  class Published < Base
3
3
  include Reloadable
4
4
  include Singleton
5
- class << self
6
- def derivable_from(content)
7
- !content.new_record? && content.published
8
- end
9
- end
10
5
 
11
- def serialize_on(content)
6
+ def published?(content)
12
7
  true
13
8
  end
14
9
 
15
- def published?
16
- true
10
+ def enter_hook(content)
11
+ super
12
+ content[:published] = true
13
+ content[:published_at] ||= Time.now
17
14
  end
18
15
 
19
16
  def change_published_state(content, boolean)
20
17
  content[:published] = boolean
21
18
  if ! content.published
22
19
  content[:published_at] = nil
23
- content.state = Draft.instance
20
+ content.state = Factory.new(:withdrawn)
24
21
  end
25
22
  end
26
23
 
24
+ def withdraw(content)
25
+ content.state = Factory.new(:withdrawn)
26
+ end
27
+
27
28
  def set_published_at(content, new_time)
28
29
  content[:published_at] = new_time
29
30
  return if content.published_at.nil?
@@ -0,0 +1,23 @@
1
+ module ContentState
2
+ class Spam < Base
3
+ include Reloadable
4
+ include Singleton
5
+
6
+ def enter_hook(content)
7
+ super
8
+ content[:published] = false
9
+ end
10
+
11
+ def is_spam?(content)
12
+ true
13
+ end
14
+
15
+ def to_s
16
+ 'Spam'
17
+ end
18
+
19
+ def mark_as_spam(content)
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ module ContentState
2
+ class Unclassified < Base
3
+ include Reloadable
4
+ include Singleton
5
+
6
+ def published?(content)
7
+ classify(content).published?(content)
8
+ end
9
+
10
+ def is_spam?(content)
11
+ classify(content).is_spam?(content)
12
+ end
13
+
14
+ def classify(content)
15
+ content.state = case content.classify
16
+ when :ham
17
+ Factory.new(:just_presumed_ham)
18
+ when :spam
19
+ Factory.new(:presumed_spam)
20
+ else
21
+ Factory.new(:presumed_spam)
22
+ end
23
+ end
24
+
25
+ def before_save(content)
26
+ classify(content).before_save(content)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ module ContentState
2
+ class Withdrawn < Base
3
+ include Reloadable
4
+ include Singleton
5
+
6
+ def enter_hook(content)
7
+ content[:published] = false
8
+ end
9
+
10
+ def change_published_state(content, boolean)
11
+ return unless boolean
12
+ content[:published] = true
13
+ content.state = Published.instance
14
+ end
15
+
16
+ def set_published_at(content, new_time)
17
+ content[:published_at] = new_time
18
+ Trigger.remove(content, :trigger_method => 'publish!')
19
+ return if new_time.nil? || new_time <= Time.now
20
+ content.state = PublicationPending.instance
21
+ end
22
+
23
+ def withdrawn?
24
+ true
25
+ end
26
+ end
27
+ end
28
+
@@ -2,7 +2,6 @@ class EmailNotifier < ActiveRecord::Observer
2
2
  observe Article, Comment
3
3
 
4
4
  def after_save(content)
5
- return true unless content.just_published?
6
5
  content.send_notifications
7
6
  true
8
7
  end
@@ -0,0 +1,151 @@
1
+ class Feedback < Content
2
+ # Empty, for now, ready to hoist up methods from Comment & Trackback
3
+ include TypoGuid
4
+ validates_age_of :article_id
5
+
6
+ def self.default_order
7
+ 'created_at ASC'
8
+ end
9
+
10
+ def initialize(*args, &block)
11
+ super(*args, &block)
12
+ self.state = ContentState::Unclassified.instance
13
+ end
14
+
15
+ def location(anchor=:ignored, only_path=true)
16
+ blog.url_for(article, "#{self.class.to_s.downcase}-#{id}", only_path)
17
+ end
18
+
19
+ before_create :create_guid, :make_nofollow, :article_allows_this_feedback
20
+ before_save :correct_url
21
+
22
+ def correct_url
23
+ if !url.blank? && url !~ /^http:\/\//
24
+ self.url = 'http://' + url
25
+ end
26
+ end
27
+
28
+ def article_allows_this_feedback
29
+ article && blog_allows_feedback? && article_allows_feedback?
30
+ end
31
+
32
+ def blog_allows_feedback?
33
+ true
34
+ end
35
+
36
+ def akismet_options
37
+ {:user_ip => ip,
38
+ :comment_type => self.class.to_s.downcase,
39
+ :comment_author => originator,
40
+ :comment_author_email => email,
41
+ :comment_author_url => url,
42
+ :comment_content => body}.merge(additional_akismet_options)
43
+ end
44
+
45
+ def additional_akismet_options
46
+ { }
47
+ end
48
+
49
+ def spam_fields
50
+ [:title, :body, :ip, :url]
51
+ end
52
+
53
+ def spam?
54
+ state.is_spam?(self)
55
+ end
56
+
57
+ # is_spam? checks to see if this is spam.
58
+ #
59
+ # options are passed on to Akismet. Recommended options (when available) are:
60
+ #
61
+ # :permalink => the article's URL
62
+ # :user_agent => the poster's UserAgent string
63
+ # :referer => the poster's Referer string
64
+ #
65
+
66
+ def is_spam?(options={})
67
+ return false unless blog.sp_global
68
+ sp_is_spam?(options) || akismet_is_spam?(options)
69
+ end
70
+
71
+ def classify
72
+ return :ham unless blog.sp_global
73
+ test_result = is_spam?
74
+
75
+ # Yeah, three state logic is evil...
76
+ case is_spam?
77
+ when nil; :spam
78
+ when true; :spam
79
+ when false; :ham
80
+ end
81
+ end
82
+
83
+ def sp_is_spam?(options={})
84
+ sp = SpamProtection.new(blog)
85
+ spam_fields.any? do |field|
86
+ sp.is_spam?(self.send(field))
87
+ end
88
+ end
89
+
90
+ def akismet
91
+ Akismet.new(blog.sp_akismet_key, blog.canonical_server_url)
92
+ end
93
+
94
+ def akismet_is_spam?(options={})
95
+ return false if blog.sp_akismet_key.blank?
96
+ begin
97
+ Timeout.timeout(5) do
98
+ akismet.commentCheck(akismet_options)
99
+ end
100
+ rescue Timeout::Error => e
101
+ nil
102
+ end
103
+ end
104
+
105
+ def mark_as_ham
106
+ state.mark_as_ham(self)
107
+ end
108
+
109
+ def mark_as_ham!
110
+ mark_as_ham
111
+ save!
112
+ end
113
+
114
+ def mark_as_spam
115
+ state.mark_as_spam(self)
116
+ end
117
+
118
+ def mark_as_spam!
119
+ mark_as_spam
120
+ save
121
+ end
122
+
123
+ def report_as_spam
124
+ return if blog.sp_akismet_key.blank?
125
+ Timeout.timeout(5) { akismet.submitSpam(akismet_options) }
126
+ end
127
+
128
+ def report_as_ham
129
+ return if blog.sp_akismet_key.blank?
130
+ Timeout.timeout(5) { akismet.submitHam(akismet_options) }
131
+ end
132
+
133
+ def set_spam(is_spam, options ={})
134
+ return if blog.sp_akismet_key.blank?
135
+ Timeout.timeout(5) { is_spam ? report_as_spam : report_as_ham }
136
+ end
137
+
138
+ def withdraw!
139
+ withdraw
140
+ self.save!
141
+ end
142
+
143
+ def confirm_classification
144
+ state.confirm_classification(self)
145
+ end
146
+
147
+ def confirm_classification!
148
+ state.confirm_classification(self)
149
+ self.save
150
+ end
151
+ end
@@ -1,18 +1,11 @@
1
1
  require_dependency 'spam_protection'
2
2
 
3
- class Trackback < Content
4
- include TypoGuid
5
- belongs_to :article, :counter_cache => true
3
+ class Trackback < Feedback
4
+ belongs_to :article
6
5
 
7
6
  content_fields :excerpt
8
7
 
9
- validates_age_of :article_id
10
8
  validates_presence_of :title, :excerpt, :url
11
- validate_on_create :article_is_pingable
12
-
13
- def self.default_order
14
- 'created_at ASC'
15
- end
16
9
 
17
10
  def initialize(*args, &block)
18
11
  super(*args, &block)
@@ -20,12 +13,7 @@ class Trackback < Content
20
13
  self.blog_name ||= ""
21
14
  end
22
15
 
23
- def location(anchor=:ignored, only_path=true)
24
- blog.url_for(article, "trackback-#{id}", only_path)
25
- end
26
-
27
- protected
28
- before_create :make_nofollow, :process_trackback, :create_guid
16
+ before_create :process_trackback
29
17
 
30
18
  def make_nofollow
31
19
  self.blog_name = blog_name.strip_html
@@ -40,23 +28,28 @@ class Trackback < Content
40
28
  end
41
29
  end
42
30
 
43
- def article_is_pingable
44
- return if article.nil?
45
- if blog.global_pings_disable
46
- errors.add(:article, "Pings are disabled")
47
- end
48
- unless article.allow_pings?
49
- errors.add(:article, "Article is not pingable")
50
- end
31
+ def article_allows_feedback?
32
+ return true if article.allow_pings?
33
+ errors.add(:article, 'Article is not pingable')
34
+ false
51
35
  end
52
36
 
53
- def akismet_options
54
- {:user_ip => ip, :comment_type => 'trackback', :comment_author => blog_name, :comment_author_email => nil,
55
- :comment_author_url => url, :comment_content => excerpt}
37
+ def blog_allows_feedback?
38
+ return true unless blog.global_pings_disable
39
+ errors.add(:article, "Pings are disabled")
40
+ false
56
41
  end
57
-
58
- def spam_fields
59
- [:title, :excerpt, :ip, :url]
42
+
43
+ def originator
44
+ blog_name
45
+ end
46
+
47
+ def body
48
+ excerpt
49
+ end
50
+
51
+ def body=(newval)
52
+ self.excerpt = newval
60
53
  end
61
54
  end
62
55
 
@@ -1,15 +1,15 @@
1
- <tr class="feedbackbody">
1
+ <tr class="<%= state_class(item) %>">
2
2
  <td class="field"><input class= "feedback_check" type="checkbox" name="feedback_check[<%= item.id %>]"/></td>
3
- <td class="field"><%= item.published ? "Yes" : "<strong>No</strong>" %></td>
3
+ <td class="state"><%= item.state %></td>
4
4
  <td class="field"><%=h item.class %></td>
5
5
  <td class="field"><%= link_to_article_edit item.article %></td>
6
6
  <td class="field">
7
7
  <%=h (item.author || '(unknown)').slice(0,40) %><br/>
8
- <a href=<%=h item.url%>><%=h truncate(item.url.to_s)%></a><br/>
9
- <%=h item.email.to_s.slice(0,40) %>
8
+ <a href=<%=h item.url%>><%=h truncate(item.url.to_s)%></a><br/>
9
+ <%=h item.email.to_s.slice(0,40) %>
10
10
  </td>
11
11
  <td class="field"><%=h truncate(item.body,80)%></td>
12
12
  <td class="field"><%=h item.ip %></td>
13
13
  <td class="field"><%=h distance_of_time_in_words_to_now(item.created_at) %> ago</td>
14
14
  <td class="field"><%= link_to image_tag('delete'), {:action => 'delete', :id => item.id, :search => params[:search], :page => params[:page] }, :confirm => "Are you sure?", :post => true %></td>
15
- </tr>
15
+ </tr>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <% content_for('tasks') do %>
4
4
  <%= task_showmod 'Show only unpublished feedback' %>
5
- <% end %>
5
+ <% end %>
6
6
 
7
7
  <div clas="search">
8
8
  <form>
@@ -10,24 +10,25 @@
10
10
  </form>
11
11
  </div>
12
12
 
13
- <div class="list">
13
+ <div class="list">
14
14
  <%= form_tag({:action => 'bulkops'}, :method => :post) %>
15
15
  <br/>
16
16
  <%= submit_tag "Delete Checked Items" %> &nbsp;
17
- <%= submit_tag "Publish Checked Items" %>
18
- <%= submit_tag "Unpublish Checked Items" %>
17
+ <%= submit_tag "Mark Checked Items as Ham" %>
18
+ <%= submit_tag "Mark Checked Items as Spam" %>
19
+ <%= submit_tag "Confirm Classification of Checked Items" %>
19
20
 
20
21
  <%= hidden_field_tag "search", params[:search]%>
21
22
  <%= hidden_field_tag "page", params[:page]%>
22
23
 
23
24
  <table>
24
25
  <tr>
25
- <th><input class="feedback_check" type="checkbox" name="checkall" id="checkall" onclick="check_all(this);"/></th>
26
- <th>Pub</th>
27
- <th>Type</th>
28
- <th>Article</th>
26
+ <th><input class="feedback_check" type="checkbox" name="checkall" id="checkall" onclick="check_all(this);"/></th>
27
+ <th>State</th>
28
+ <th>Type</th>
29
+ <th>Article</th>
29
30
  <th>Author</th>
30
- <th>Body</th>
31
+ <th>Body</th>
31
32
  <th>IP</th>
32
33
  <th>Posted date</th>
33
34
  <th>Delete</th>
@@ -35,8 +36,9 @@
35
36
  <%= render :partial => 'item', :collection => @feedback %>
36
37
  </table>
37
38
  <%= submit_tag "Delete Checked Items" %> &nbsp;
38
- <%= submit_tag "Publish Checked Items" %>
39
- <%= submit_tag "Unpublish Checked Items" %>
39
+ <%= submit_tag "Mark Checked Items as Ham" %>
40
+ <%= submit_tag "Mark Checked Items as Spam" %>
41
+ <%= submit_tag "Confirm Classification of Checked Items" %>
40
42
  </form>
41
43
  </div>
42
44