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.
- data/app/controllers/admin/comments_controller.rb +1 -1
- data/app/controllers/admin/content_controller.rb +1 -3
- data/app/controllers/admin/feedback_controller.rb +36 -31
- data/app/controllers/admin/sidebar_controller.rb +13 -2
- data/app/controllers/admin/users_controller.rb +2 -1
- data/app/controllers/articles_controller.rb +10 -19
- data/app/controllers/xml_controller.rb +2 -2
- data/app/helpers/admin/base_helper.rb +7 -3
- data/app/helpers/application_helper.rb +2 -2
- data/app/helpers/articles_helper.rb +5 -4
- data/app/models/article.rb +16 -10
- data/app/models/blog.rb +4 -10
- data/app/models/comment.rb +17 -36
- data/app/models/content.rb +31 -53
- data/app/models/content_state/base.rb +46 -3
- data/app/models/content_state/draft.rb +2 -9
- data/app/models/content_state/ham.rb +31 -0
- data/app/models/content_state/just_marked_as_ham.rb +10 -0
- data/app/models/content_state/just_marked_as_spam.rb +23 -0
- data/app/models/content_state/just_presumed_ham.rb +37 -0
- data/app/models/content_state/just_published.rb +15 -8
- data/app/models/content_state/new.rb +3 -10
- data/app/models/content_state/presumed_ham.rb +27 -0
- data/app/models/content_state/presumed_spam.rb +31 -0
- data/app/models/content_state/publication_pending.rb +7 -9
- data/app/models/content_state/published.rb +10 -9
- data/app/models/content_state/spam.rb +23 -0
- data/app/models/content_state/unclassified.rb +29 -0
- data/app/models/content_state/withdrawn.rb +28 -0
- data/app/models/email_notifier.rb +0 -1
- data/app/models/feedback.rb +151 -0
- data/app/models/trackback.rb +22 -29
- data/app/views/admin/feedback/_item.rhtml +5 -5
- data/app/views/admin/feedback/list.rhtml +13 -11
- data/app/views/admin/general/index.rhtml +8 -4
- data/app/views/admin/users/show.rhtml +7 -1
- data/app/views/articles/read.rhtml +2 -2
- data/bin/typo +7 -6
- data/components/plugins/sidebars/recent_comments_controller.rb +1 -1
- data/config/environment.rb +2 -0
- data/db/migrate/046_fixup_forthcoming_publications.rb +1 -1
- data/db/migrate/048_remove_count_caching.rb +31 -0
- data/db/migrate/049_move_feedback_to_new_state_machine.rb +33 -0
- data/db/schema.mysql-v3.sql +3 -4
- data/db/schema.mysql.sql +3 -4
- data/db/schema.postgresql.sql +3 -4
- data/db/schema.rb +12 -17
- data/db/schema.sqlite.sql +3 -4
- data/db/schema.sqlserver.sql +3 -4
- data/db/schema_version +1 -1
- data/doc/Installer.txt +4 -0
- data/lib/jabber_notify.rb +6 -2
- data/lib/sidebars/plugin.rb +2 -1
- data/lib/tasks/release.rake +5 -4
- data/lib/typo_version.rb +1 -1
- data/public/stylesheets/administration.css +22 -2
- data/test/fixtures/blogs.yml +2 -0
- data/test/fixtures/contents.yml +7 -7
- data/test/functional/admin/users_controller_test.rb +3 -0
- data/test/functional/articles_controller_test.rb +16 -1
- data/test/mocks/test/xmlrpc_mock.rb +5 -4
- data/test/unit/article_test.rb +16 -4
- data/test/unit/comment_test.rb +57 -35
- data/test/unit/content_state/factory_test.rb +7 -6
- data/test/unit/ping_test.rb +14 -0
- data/test/unit/trackback_test.rb +16 -15
- metadata +26 -26
- data/config/database.yml-pgsql +0 -17
- data/config/database.yml.sqlite +0 -14
- data/config/mail.yml +0 -8
- data/config/mongrel.conf +0 -2
- data/db/converters/mt-import.rb +0 -72
- data/db/development_structure.sql +0 -691
- data/installer/rails-installer.rb +0 -527
- data/installer/rails-installer/commands.rb +0 -118
- data/installer/rails-installer/web-servers.rb +0 -110
- data/log/development.log-1 +0 -991
- data/log/development.log-2 +0 -422
- data/log/development.log-3 +0 -429
- data/log/development.log-4 +0 -174
- data/svk-commitP6cVv.tmp +0 -1
- 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
|
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
|
6
|
+
def published?(content)
|
12
7
|
true
|
13
8
|
end
|
14
9
|
|
15
|
-
def
|
16
|
-
|
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 =
|
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
|
+
|
@@ -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
|
data/app/models/trackback.rb
CHANGED
@@ -1,18 +1,11 @@
|
|
1
1
|
require_dependency 'spam_protection'
|
2
2
|
|
3
|
-
class Trackback <
|
4
|
-
|
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
|
-
|
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
|
44
|
-
return if article.
|
45
|
-
|
46
|
-
|
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
|
54
|
-
|
55
|
-
|
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
|
59
|
-
|
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="
|
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="
|
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
|
-
|
9
|
-
|
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" %>
|
17
|
-
<%= submit_tag "
|
18
|
-
<%= submit_tag "
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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" %>
|
38
|
-
<%= submit_tag "
|
39
|
-
<%= submit_tag "
|
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
|
|