typo 3.99.3 → 3.99.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/README +1 -39
  2. data/app/controllers/admin/feedback_controller.rb +71 -0
  3. data/app/controllers/articles_controller.rb +11 -0
  4. data/app/helpers/admin/feedback_helper.rb +9 -0
  5. data/app/helpers/application_helper.rb +5 -1
  6. data/app/models/article.rb +21 -1
  7. data/app/models/blog.rb +25 -1
  8. data/app/models/comment.rb +12 -2
  9. data/app/models/content.rb +63 -4
  10. data/app/models/ping.rb +3 -3
  11. data/app/models/text_filter.rb +1 -1
  12. data/app/models/trackback.rb +12 -1
  13. data/app/views/admin/content/_form.rhtml +2 -2
  14. data/app/views/admin/feedback/_item.rhtml +15 -0
  15. data/app/views/admin/feedback/list.rhtml +46 -0
  16. data/app/views/admin/general/index.rhtml +26 -0
  17. data/app/views/articles/_comment.rhtml +3 -0
  18. data/app/views/articles/read.rhtml +2 -2
  19. data/app/views/layouts/administration.rhtml +1 -0
  20. data/bin/typo +3 -23
  21. data/components/plugins/sidebars/archives_controller.rb +1 -1
  22. data/components/plugins/sidebars/xml_controller.rb +1 -1
  23. data/components/plugins/textfilters/flickr_controller.rb +1 -1
  24. data/components/plugins/textfilters/sparkline_controller.rb +3 -2
  25. data/components/plugins/textfilters/textile_controller.rb +6 -0
  26. data/config/mongrel.conf +2 -0
  27. data/db/converters/wordpress2.rb +291 -0
  28. data/db/migrate/022_superclass_trackbacks.rb +1 -0
  29. data/db/migrate/023_superclass_pages.rb +4 -3
  30. data/db/schema.rb +4 -4
  31. data/doc/Installer.txt +81 -6
  32. data/doc/typo-4.0-release-notes.txt +135 -0
  33. data/installer/rails-installer.rb +22 -3
  34. data/installer/rails-installer/commands.rb +27 -26
  35. data/installer/rails-installer/web-servers.rb +2 -0
  36. data/lib/sidebars/plugin.rb +10 -8
  37. data/lib/tasks/release.rake +1 -1
  38. data/lib/typo_version.rb +1 -1
  39. data/public/javascripts/dragdrop.js +252 -63
  40. data/public/javascripts/effects.js +15 -10
  41. data/public/javascripts/prototype.js +59 -38
  42. data/public/javascripts/typo.js +10 -0
  43. data/public/stylesheets/administration.css +111 -66
  44. data/test/functional/admin/feedback_controller_test.rb +24 -0
  45. data/test/mocks/test/http_mock.rb +2 -1
  46. data/test/unit/article_test.rb +6 -0
  47. data/test/unit/comment_test.rb +12 -9
  48. data/test/unit/ping_test.rb +1 -1
  49. data/test/unit/trackback_test.rb +3 -5
  50. data/themes/azure/stylesheets/azure.css +7 -0
  51. data/themes/scribbish/views/articles/_article.rhtml +1 -1
  52. data/themes/scribbish/views/articles/_comment_form.rhtml +1 -1
  53. data/vendor/akismet/Akismet.rb +36 -17
  54. data/vendor/plugins/expiring_action_cache/lib/actionparamcache.rb +3 -1
  55. metadata +11 -42
  56. data/installer/rails-installer/web-server.rb +0 -108
  57. data/tmp/cache/META/DATA/ACTION_PARAM/10.1.0.181/articles/index/.cache +0 -537
  58. data/tmp/cache/META/DATA/ACTION_PARAM/localhost/articles/index/.cache +0 -537
  59. data/tmp/cache/META/DATA/ACTION_PARAM/localhost/xml/feed/format=atom&type=feed.cache +0 -671
  60. data/tmp/cache/META/DATA/ACTION_PARAM/localhost/xml/feed/format=rss20&type=feed.cache +0 -401
  61. data/tmp/cache/META/META/ACTION_PARAM/10.1.0.181/articles/index/.cache +0 -2
  62. data/tmp/cache/META/META/ACTION_PARAM/localhost/articles/index/.cache +0 -2
  63. data/tmp/cache/META/META/ACTION_PARAM/localhost/xml/feed/format=atom&type=feed.cache +0 -2
  64. data/tmp/cache/META/META/ACTION_PARAM/localhost/xml/feed/format=rss20&type=feed.cache +0 -2
data/README CHANGED
@@ -24,45 +24,7 @@ Currently you need all of those things to get typo to run:
24
24
  Installation
25
25
  ============
26
26
 
27
- Unpack the tgz or zip in some directory.
28
-
29
- Decide which database to use. We support Sqlite, MySQL, and Postgres,
30
- but Sqlite doesn't have full support for database migrations in Rails
31
- 0.13.1.
32
-
33
- * Create a database for typo. You can find schemas in the db/ folder.
34
- * Create config/database.yml using database.yml.example to reflect your
35
- newly created database configuration
36
- * Run script/server -e production and see if it works
37
- * Point your browser to http://your.domain.com:3000/ and follow the
38
- install process
39
-
40
- Typo, like all Rails apps, doesn't work well as a CGI. Seriously consider
41
- using FastCGI instead. To deploy on FastCGI you will need to follow the setup
42
- instructions on the typo page at http://www.typosphere.org/trac/wiki/FastCgi
43
-
44
- By default, Typo runs in *development* mode. This is very useful for
45
- developers, but it can cause horrible performance for users who aren't
46
- changing Typo's code. Production mode can easily be 20x as fast as development
47
- mode. To change the default, edit the second line of config/environment.rb and
48
- change 'development' to 'production', or follow the directions on the FastCGI
49
- configuration page on the Typo wiki.
50
-
51
- Permissions
52
- ===========
53
-
54
- Typo needs write access to several directories in order to function
55
- correctly. These need to be writable by the user that runs the Typo
56
- process--in a hosted environment this may be your user; on dedicated
57
- systems it may be something like 'httpd' or 'www-data'.
58
-
59
- The specific directories in question are 'log/' (and everything underneath
60
- it), 'cache/', and 'public/'. Strictly speaking, Rails will continue to work
61
- if public isn't writable, but Typo's page caching code will work properly and
62
- this will cause Typo to be slower and use much more CPU time. For the security
63
- conscious, Rails really only needs the ability to change a half-dozen files
64
- and subdirectories under public/, ask on the Typo mailing list for more
65
- details.
27
+ See doc/Installer.text and doc/typo-4.0-release-notes.txt.
66
28
 
67
29
  Usage
68
30
  ======
@@ -0,0 +1,71 @@
1
+ class Admin::FeedbackController < Admin::BaseController
2
+ def index
3
+ conditions = ["(contents.type = 'Comment' or contents.type = 'Trackback')"]
4
+
5
+ if params[:search]
6
+ search_sql = "%#{params[:search]}%"
7
+ conditions.first << ' and (url like ? or author like ? or title like ? or ip like ? or email like ?)'
8
+ 5.times { conditions.push search_sql }
9
+ end
10
+
11
+ if params[:published] == 'f'
12
+ conditions.first << ' and (published = ?)'
13
+ conditions.push false
14
+ end
15
+
16
+ @pages, @feedback = paginate(:contents,
17
+ :order => 'contents.created_at desc',
18
+ :conditions => conditions,
19
+ :per_page => 40)
20
+
21
+ render_action 'list'
22
+ end
23
+
24
+ def delete
25
+ if request.post?
26
+ feedback = Content.find(params[:id])
27
+ if feedback.kind_of? Comment or feedback.kind_of? Trackback
28
+ feedback.destroy
29
+ flash[:notice] = "Deleted"
30
+ else
31
+ flash[:notice] = "Not found"
32
+ end
33
+ end
34
+ redirect_to :action => 'index', :page => params[:page], :search => params[:search]
35
+ end
36
+
37
+ def bulkops
38
+ STDERR.puts "Bulkops: #{params.inspect}"
39
+
40
+ ids = (params[:feedback_check]||{}).keys.map(&:to_i)
41
+
42
+ case params[:commit]
43
+ when 'Delete Checked Items'
44
+ count = 0
45
+ ids.each do |id|
46
+ count += Content.delete(id)
47
+ end
48
+ flash[:notice] = "Deleted #{count} item(s)"
49
+ when 'Publish Checked Items'
50
+ ids.each do |id|
51
+ feedback = Content.find(id)
52
+ feedback.attributes[:published] = true
53
+ feedback.set_spam(false)
54
+ feedback.save
55
+ end
56
+ flash[:notice]= "Published #{ids.size} item(s)"
57
+ when 'Unpublish Checked Items'
58
+ ids.each do |id|
59
+ feedback = Content.find(id)
60
+ feedback.withdraw!
61
+ feedback.set_spam(true)
62
+ feedback.save
63
+ end
64
+ flash[:notice]= "Unpublished #{ids.size} item(s)"
65
+ else
66
+ flash[:notice] = "Not implemented"
67
+ end
68
+
69
+ redirect_to :action => 'index', :page => params[:page], :search => params[:search]
70
+ end
71
+ end
@@ -99,6 +99,17 @@ class ArticlesController < ContentController
99
99
  @article = this_blog.published_articles.find(params[:id])
100
100
  @comment = @article.comments.build(params[:comment])
101
101
  @comment.user = session[:user]
102
+
103
+ spam_options = {
104
+ :user_agent => request.env['HTTP_USER_AGENT'],
105
+ :referrer => request.env['HTTP_REFERER'],
106
+ :permalink => this_blog.article_url(@article, false)}
107
+
108
+ if @comment.is_spam? spam_options
109
+ STDERR.puts "Moderating comment as spam!"
110
+ @comment.withdraw
111
+ end
112
+
102
113
  @comment.save!
103
114
  add_to_cookies(:author, @comment.author)
104
115
  add_to_cookies(:url, @comment.url)
@@ -0,0 +1,9 @@
1
+ module Admin::FeedbackHelper
2
+ def link_to_article_edit(article)
3
+ link_to truncate(article.title, 60), :controller => 'content', :action => 'edit', :id => article.id
4
+ end
5
+
6
+ def task_showmod(title)
7
+ content_tag :li, link_to(title, :published => 'f', :search => params[:search])
8
+ end
9
+ end
@@ -93,6 +93,10 @@ module ApplicationHelper
93
93
  end
94
94
 
95
95
  def markup_help_popup(markup, text)
96
- "<a href=\"#{url_for :controller => '/articles', :action => 'markup_help', :id => markup.id}\" onClick=\"return popup(this, 'Typo Markup Help')\">#{text}</a>"
96
+ if markup and markup.commenthelp.size > 1
97
+ "<a href=\"#{url_for :controller => '/articles', :action => 'markup_help', :id => markup.id}\" onClick=\"return popup(this, 'Typo Markup Help')\">#{text}</a>"
98
+ else
99
+ ''
100
+ end
97
101
  end
98
102
  end
@@ -120,7 +120,7 @@ class Article < Content
120
120
  def keywords_to_tags
121
121
  Article.transaction do
122
122
  tags.clear
123
- keywords.to_s.scan(/((['"]).*?\2|\w+)/).collect do |x|
123
+ keywords.to_s.scan(/((['"]).*?\2|[\.\w]+)/).collect do |x|
124
124
  x.first.tr("\"'", '')
125
125
  end.uniq.each do |tagword|
126
126
  tags << Tag.get(tagword)
@@ -146,15 +146,34 @@ class Article < Content
146
146
  end
147
147
  end
148
148
 
149
+ def comments_closed?
150
+ if self.allow_comments?
151
+ if !self.blog.sp_article_auto_close.zero? and self.created_at.to_i < self.blog.sp_article_auto_close.days.ago.to_i
152
+ return true
153
+ else
154
+ return false
155
+ end
156
+ else
157
+ return true
158
+ end
159
+ end
160
+
149
161
  protected
150
162
 
151
163
  before_create :set_defaults, :create_guid, :add_notifications
164
+ before_save :set_published_at
152
165
  after_save :keywords_to_tags
153
166
 
154
167
  def correct_counts
155
168
  self.comments_count = self.comments_count
156
169
  self.trackbacks_count = self.trackbacks_count
157
170
  end
171
+
172
+ def set_published_at
173
+ if self.published and self[:published_at].nil?
174
+ self[:published_at] = self.created_at || Time.now
175
+ end
176
+ end
158
177
 
159
178
  def set_defaults
160
179
  if self.attributes.include?("permalink") and self.permalink.blank?
@@ -168,6 +187,7 @@ class Article < Content
168
187
  if blog && self.allow_pings.nil?
169
188
  self.allow_pings = blog.default_allow_pings
170
189
  end
190
+
171
191
  true
172
192
  end
173
193
 
data/app/models/blog.rb CHANGED
@@ -1,3 +1,17 @@
1
+ # BlogRequest is a fake Request object, created so blog.url_for will work.
2
+ # This isn't enabled yet, but it will be soon...
3
+ class BlogRequest
4
+ include Reloadable
5
+
6
+ attr_accessor :protocol, :host_with_port, :path, :symbolized_path_parameters, :relative_url_root
7
+
8
+ def initialize(root)
9
+ @protocol = @host_with_port = @path = ''
10
+ @symbolized_path_parameters = {}
11
+ @relative_url_root = root.gsub(%r{/^},'')
12
+ end
13
+ end
14
+
1
15
  class Blog < ActiveRecord::Base
2
16
  include ConfigManager
3
17
 
@@ -31,6 +45,7 @@ class Blog < ActiveRecord::Base
31
45
  setting :sp_article_auto_close, :integer, 0
32
46
  setting :sp_allow_non_ajax_comments, :boolean, true
33
47
  setting :sp_url_limit, :integer, 0
48
+ setting :sp_akismet_key, :string, ''
34
49
 
35
50
  # Podcasting
36
51
  setting :itunes_explicit, :boolean, false
@@ -53,6 +68,7 @@ class Blog < ActiveRecord::Base
53
68
  setting :show_extended_on_rss, :boolean, true
54
69
  setting :theme, :string, 'azure'
55
70
  setting :use_gravatar, :boolean, false
71
+ setting :global_pings_disable, :boolean, false
56
72
  setting :ping_urls, :string, "http://rpc.technorati.com/rpc/ping\nhttp://ping.blo.gs/\nhttp://rpc.weblogs.com/RPC2"
57
73
  setting :send_outbound_pings, :boolean, true
58
74
  setting :email_from, :string, 'typo@example.com'
@@ -69,7 +85,14 @@ class Blog < ActiveRecord::Base
69
85
  settings[:blog_id] = self.id
70
86
  article_id = settings[:id]
71
87
  settings.delete(:id)
72
- published_articles.find(article_id).trackbacks.create!(settings)
88
+ trackback = published_articles.find(article_id).trackbacks.create!(settings)
89
+
90
+ if trackback.is_spam?
91
+ STDERR.puts "Moderating trackback as spam!"
92
+ trackback.withdraw!
93
+ end
94
+
95
+ trackback
73
96
  end
74
97
 
75
98
 
@@ -156,6 +179,7 @@ class Blog < ActiveRecord::Base
156
179
  private
157
180
 
158
181
  def request
182
+ #BlogRequest.new(self.canonical_server_url)
159
183
  controller.request rescue ActionController::TestRequest.new
160
184
  end
161
185
  end
@@ -1,5 +1,6 @@
1
1
  require_dependency 'spam_protection'
2
2
  require 'sanitize'
3
+ require 'timeout'
3
4
 
4
5
  class Comment < Content
5
6
  include TypoGuid
@@ -10,10 +11,10 @@ class Comment < Content
10
11
  belongs_to :user
11
12
 
12
13
  validates_presence_of :author, :body
13
- validates_against_spamdb :body, :url, :ip
14
14
  validates_age_of :article_id
15
15
  validate_on_create :check_article_is_open_to_comments
16
16
 
17
+
17
18
  def self.default_order
18
19
  'created_at ASC'
19
20
  end
@@ -50,7 +51,7 @@ class Comment < Content
50
51
  end
51
52
 
52
53
  def body_html_postprocess(value, controller)
53
- sanitize(controller.send(:auto_link, value),'a href, b, br, i, p, em, strong, pre, code')
54
+ sanitize(controller.send(:auto_link, value),'a href, b, br, i, p, em, strong, pre, code, ol, ul, li')
54
55
  end
55
56
 
56
57
  def default_text_filter_config_key
@@ -72,4 +73,13 @@ class Comment < Content
72
73
  self.author = author.nofollowify
73
74
  self.body_html = body_html.to_s.nofollowify
74
75
  end
76
+
77
+ def akismet_options
78
+ {:user_ip => ip, :comment_type => 'comment', :comment_author => author, :comment_author_email => email,
79
+ :comment_author_url => url, :comment_content => body}
80
+ end
81
+
82
+ def spam_fields
83
+ [:body, :url, :ip]
84
+ end
75
85
  end
@@ -1,5 +1,7 @@
1
1
  require 'observer'
2
2
  require 'set'
3
+ require 'Akismet'
4
+
3
5
  class Content < ActiveRecord::Base
4
6
  include Observable
5
7
 
@@ -20,11 +22,11 @@ class Content < ActiveRecord::Base
20
22
 
21
23
  @@content_fields = Hash.new
22
24
  @@html_map = Hash.new
23
-
25
+
24
26
  def initialize(*args)
25
27
  super(*args)
26
-
27
- #
28
+
29
+ #
28
30
  if self.blog_id == nil or self.blog_id == 0
29
31
  self.blog_id = Blog.default
30
32
  end
@@ -170,7 +172,7 @@ class Content < ActiveRecord::Base
170
172
  end
171
173
 
172
174
  def text_filter=(filter)
173
- self[:text_filter] = filter.to_text_filter
175
+ self[:text_filter_id] = filter.to_text_filter.id
174
176
  end
175
177
 
176
178
  def blog
@@ -182,6 +184,16 @@ class Content < ActiveRecord::Base
182
184
  self.save!
183
185
  end
184
186
 
187
+ def withdraw
188
+ self.published = false
189
+ self.published_at = nil
190
+ end
191
+
192
+ def withdraw!
193
+ self.withdraw
194
+ self.save!
195
+ end
196
+
185
197
  def published=(a_boolean)
186
198
  self[:published] = a_boolean
187
199
  state.change_published_state(self, a_boolean)
@@ -219,6 +231,53 @@ class Content < ActiveRecord::Base
219
231
  def send_notifications(controller = nil)
220
232
  state.send_notifications(self, controller || blog.controller)
221
233
  end
234
+
235
+ # is_spam? checks to see if this is spam. This really belongs in a 'feedback' class
236
+ # that is the parent of Comment and Trackback, but we aren't going to build one right
237
+ # now.
238
+ #
239
+ # options are passed on to Akismet. Recommended options (when available) are:
240
+ #
241
+ # :permalink => the article's URL
242
+ # :user_agent => the poster's UserAgent string
243
+ # :referer => the poster's Referer string
244
+ #
245
+ def is_spam?(options={})
246
+ return false unless blog.sp_global
247
+
248
+ sp = SpamProtection.new(blog)
249
+ spam = false
250
+
251
+ # Check fields against the blacklist.
252
+ spam_fields.each do |field|
253
+ spam ||= sp.is_spam? self[field]
254
+ end
255
+
256
+ # Attempt to use Akismet. Timeout after 5 seconds if we can't contact them.
257
+ unless blog.sp_akismet_key.blank?
258
+ Timeout.timeout(5) do
259
+ akismet = Akismet.new(blog.sp_akismet_key,blog.canonical_server_url)
260
+ spam ||= akismet.commentCheck(akismet_options.merge(options))
261
+ end
262
+ end
263
+
264
+ spam == true
265
+ end
266
+
267
+ def set_spam(is_spam, options ={})
268
+ unless blog.sp_akismet_key.blank?
269
+ Timeout.timeout(5) do
270
+ akismet = Akismet.new(blog.sp_akismet_key,blog.canonical_server_url)
271
+ if is_spam
272
+ STDERR.puts "** submitting spam for #{id}"
273
+ akismet.submitSpam(akismet_options.merge(options))
274
+ else
275
+ STDERR.puts "** submitting ham for #{id}"
276
+ akismet.submitHam(akismet_options.merge(options))
277
+ end
278
+ end
279
+ end
280
+ end
222
281
  end
223
282
 
224
283
  class Object; def to_text_filter; TextFilter.find_by_name(self.to_s); end; end
data/app/models/ping.rb CHANGED
@@ -97,10 +97,10 @@ class Ping < ActiveRecord::Base
97
97
  t = Thread.start do
98
98
  trackback_uri = URI.parse(trackback_url)
99
99
 
100
- post = "title=#{URI.escape(article.title)}"
101
- post << "&excerpt=#{URI.escape(article.body_html.strip_html[0..254])}"
100
+ post = "title=#{CGI.escape(article.title)}"
101
+ post << "&excerpt=#{CGI.escape(article.body_html.strip_html[0..254])}"
102
102
  post << "&url=#{origin_url}"
103
- post << "&blog_name=#{URI.escape(article.blog.blog_name)}"
103
+ post << "&blog_name=#{CGI.escape(article.blog.blog_name)}"
104
104
 
105
105
  Net::HTTP.start(trackback_uri.host, trackback_uri.port) do |http|
106
106
  path = trackback_uri.path
@@ -100,7 +100,7 @@ class TextFilter < ActiveRecord::Base
100
100
 
101
101
  help_text = help.collect do |f|
102
102
  f.help_text.blank? ? '' : "#{BlueCloth.new(f.help_text).to_html}\n"
103
- end
103
+ end.join("\n")
104
104
 
105
105
  return help_text
106
106
  end
@@ -7,7 +7,6 @@ class Trackback < Content
7
7
  content_fields :excerpt
8
8
 
9
9
  validates_age_of :article_id
10
- validates_against_spamdb :title, :excerpt, :ip, :url
11
10
  validates_presence_of :title, :excerpt, :url
12
11
  validate_on_create :article_is_pingable
13
12
 
@@ -43,9 +42,21 @@ class Trackback < Content
43
42
 
44
43
  def article_is_pingable
45
44
  return if article.nil?
45
+ if blog.global_pings_disable
46
+ errors.add(:article, "Pings are disabled")
47
+ end
46
48
  unless article.allow_pings?
47
49
  errors.add(:article, "Article is not pingable")
48
50
  end
49
51
  end
52
+
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}
56
+ end
57
+
58
+ def spam_fields
59
+ [:title, :excerpt, :ip, :url]
60
+ end
50
61
  end
51
62