typo 3.99.3 → 3.99.4

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 (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