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
@@ -21,7 +21,7 @@
21
21
  </select>
22
22
  </p>
23
23
  <p>
24
- <label for="article_keywords">Keywords:</label><br/>
24
+ <label for="article_keywords">Tags:</label><br/>
25
25
  <%= text_field 'article', 'keywords' %>
26
26
  </p>
27
27
  <p>
@@ -35,7 +35,7 @@
35
35
  <a href="#" onclick="Element.toggle('advanced'); return false;">Toggle Advanced Options (+/-)</a>
36
36
  <div id="advanced" style="display:none;">
37
37
  <label for="article_allow_comments">Allow comments: </label><%= check_box 'article', 'allow_comments' %><br />
38
- <label for="article_allow_pings">Allow pings: </label><%= check_box 'article', 'allow_pings' %><br />
38
+ <label for="article_allow_pings">Allow trackbacks: </label><%= check_box 'article', 'allow_pings' %><br />
39
39
  <label for="article_published">Published:</label><%= check_box 'article', 'published' %><br />
40
40
  <label for="article_text_filter">Textfilter: </label><%= select 'article', 'text_filter', text_filter_options %><br />
41
41
  <label for="article_published_at">Publish at:</label><%= datetime_select 'article', 'published_at', :include_blank => true %><br />
@@ -0,0 +1,15 @@
1
+ <tr class="feedbackbody">
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>
4
+ <td class="field"><%=h item.class %></td>
5
+ <td class="field"><%= link_to_article_edit item.article %></td>
6
+ <td class="field">
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) %>
10
+ </td>
11
+ <td class="field"><%=h truncate(item.body,80)%></td>
12
+ <td class="field"><%=h item.ip %></td>
13
+ <td class="field"><%=h distance_of_time_in_words_to_now(item.created_at) %> ago</td>
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>
@@ -0,0 +1,46 @@
1
+ <% @page_heading = "Comments and Trackbacks for #{ this_blog.settings['blog_name'] }" %>
2
+
3
+ <% content_for('tasks') do %>
4
+ <%= task_showmod 'Show only unpublished feedback' %>
5
+ <% end %>
6
+
7
+ <div clas="search">
8
+ <form>
9
+ <label for="search">Feedback Search:</label><input type="text" id="search" name="search" value="<%=h params[:search] %>" size="15" />
10
+ </form>
11
+ </div>
12
+
13
+ <div class="list">
14
+ <%= form_tag({:action => 'bulkops'}, :method => :post) %>
15
+ <br/>
16
+ <%= submit_tag "Delete Checked Items" %> &nbsp;
17
+ <%= submit_tag "Publish Checked Items" %>
18
+ <%= submit_tag "Unpublish Checked Items" %>
19
+
20
+ <%= hidden_field_tag "search", params[:search]%>
21
+ <%= hidden_field_tag "page", params[:page]%>
22
+
23
+ <table>
24
+ <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>
29
+ <th>Author</th>
30
+ <th>Body</th>
31
+ <th>IP</th>
32
+ <th>Posted date</th>
33
+ <th>Delete</th>
34
+ </tr>
35
+ <%= render :partial => 'item', :collection => @feedback %>
36
+ </table>
37
+ <%= submit_tag "Delete Checked Items" %> &nbsp;
38
+ <%= submit_tag "Publish Checked Items" %>
39
+ <%= submit_tag "Unpublish Checked Items" %>
40
+ </form>
41
+ </div>
42
+
43
+ <%= link_to "Previous page", { :page => @pages.current.previous, :search => params[:search] } if @pages.current.previous %>
44
+ <%= pagination_links(@pages, :params => {:search => params[:search]}) %>
45
+ <%= link_to "Next page", { :page => @pages.current.next, :search => params[:search] } if @pages.current.next %>
46
+
@@ -78,6 +78,22 @@
78
78
 
79
79
  <hr/>
80
80
 
81
+ <p>This setting allows you to disable trackbacks for every article in
82
+ your blog. It won't remove existing trackbacks, but it will prevent
83
+ any further attempt to add a trackback anywhere on your blog. You can
84
+ enable or disable trackbacks per-article using the article's extended
85
+ settings. See also the
86
+ <a href="#gensettings" onclick="new Effect.ScrollTo('gensettings'); return false">"Enable Trackbacks by default" setting</a> above.
87
+ </p>
88
+ <p>
89
+ <input name="setting[global_pings_disable]" id="global_pings_disable" type="checkbox" value="1" <%= 'checked="checked"' if this_blog.global_pings_disable%> />
90
+ <input name="setting[global_pings_disable]" type="hidden" value="0"/>
91
+ <label for="global_pings_disable">Disable trackbacks site-wide</label>
92
+
93
+ </p>
94
+
95
+ <hr />
96
+
81
97
  <p>Should Typo send trackbacks to websites that you link to? This should be disabled
82
98
  for private blogs, as it will leak non-public information to sites that you're discussing.
83
99
  For public blogs, there's no real point in disabling this.</p>
@@ -141,6 +157,16 @@
141
157
  <input name="setting[sp_global]" id="sp_global" type="checkbox" value="1" <%= 'checked="checked"' if this_blog.sp_global%> /><input name="setting[sp_global]" type="hidden" value="0"/>
142
158
  <label for="sp_global">Enable spam protection</label>
143
159
  </p>
160
+ <p>Typo can (optionally) use the <a href="http://akismet.com">Akismet</a> spam-filtering service. You need to register with
161
+ Akismet and receive an API key before you can use their service. If you have an Akismet key, enter it here.
162
+ </p>
163
+ <p>
164
+ <label for="sp_akismet_key">Akismet Key</label>
165
+ <input name="setting[sp_akismet_key]" id="sp_akismet_key" type="text" value="<%=h this_blog.sp_akismet_key %>"/>
166
+ </p>
167
+ <p>You can optionally disable non-Ajax comments. Typo will always use Ajax for comment submission if Javascript is enabled,
168
+ so non-Ajax comments are either from spammers or users without Javascript.
169
+ </p>
144
170
  <p>
145
171
  <input name="setting[sp_allow_non_ajax_comments]" id="sp_allow_non_ajax_comments" type="checkbox" value="1" <%= 'checked="checked"' if this_blog.sp_allow_non_ajax_comments%> />
146
172
  <input name="setting[sp_allow_non_ajax_comments]" type="hidden" value="0" />
@@ -4,4 +4,7 @@
4
4
  <%= gravatar_tag(comment.email) if this_blog.use_gravatar and comment.email %>
5
5
  <cite><strong><%= link_to_unless(comment.url.blank?, h(comment.author), comment.url) %></strong> </cite> said <%= distance_of_time_in_words comment.article.published_at, comment.created_at %> later:<br />
6
6
  <%= comment.full_html %>
7
+ <% unless comment.published -%>
8
+ <div class="spamwarning">This comment has been flagged for moderator approval. It won't appear on this blog until the author approves it.</div>
9
+ <% end -%>
7
10
  </li>
@@ -29,7 +29,7 @@
29
29
 
30
30
  <% if @article.allow_comments? or @article.comments.size > 0 -%>
31
31
  <a name="comments"></a><h4 class="blueblk">Comments</h4>
32
- <% if @article.allow_comments? -%>
32
+ <% unless @article.comments_closed? -%>
33
33
  <p class="postmetadata alt">
34
34
  <small><a href="#respond">Leave a response</a></small>
35
35
  </p>
@@ -63,7 +63,7 @@
63
63
  </small>
64
64
  </p>
65
65
 
66
- <% if @article.allow_comments? -%>
66
+ <% unless @article.comments_closed? -%>
67
67
  <%= render :partial => 'comment_box' %>
68
68
  <% else -%>
69
69
  <p>Comments are disabled</p>
@@ -30,6 +30,7 @@
30
30
  <ul id="tabs">
31
31
  <%= tab "Articles", :controller=>"/admin/content", :action => 'index' %>
32
32
  <%= tab "Pages", :controller=>"/admin/pages", :action => 'index' %>
33
+ <%= tab "Feedback", :controller=>"/admin/feedback", :action => 'index' %>
33
34
  <%= tab "Categories", :controller=>"/admin/categories", :action => 'index' %>
34
35
  <%= tab "Blacklist", :controller=>"/admin/blacklist", :action => 'index' %>
35
36
  <%= tab "Sidebar", :controller=>"/admin/sidebar", :action => 'index' %>
data/bin/typo CHANGED
@@ -7,26 +7,8 @@ class TypoInstaller < RailsInstaller
7
7
  support_location 'the Typo mailing list'
8
8
  rails_version '1.1.4'
9
9
 
10
- def install_sequence
11
- stop
12
-
13
- backup_database
14
- pre_migrate_database
15
- copy_files
16
- freeze_rails
17
- create_default_config_files
18
- create_directories
19
- create_initial_database
20
- set_initial_port_number
21
- expand_template_files
22
-
23
- migrate
10
+ def install_post_hook
24
11
  sweep_cache
25
- save
26
-
27
- run_rails_tests
28
-
29
- start
30
12
  end
31
13
 
32
14
  # Sweep the cache
@@ -38,13 +20,11 @@ class TypoInstaller < RailsInstaller
38
20
  end
39
21
 
40
22
  class SweepCache < RailsInstaller::Command
23
+ help "Sweep Typo's cache"
24
+
41
25
  def self.command(installer, *args)
42
26
  installer.sweep_cache
43
27
  end
44
-
45
- def self.help(installer)
46
- ['',"Sweep Typo's cache"]
47
- end
48
28
  end
49
29
 
50
30
  # Installer program
@@ -11,7 +11,7 @@ class Plugins::Sidebars::ArchivesController < Sidebars::ComponentPlugin
11
11
  # across all three of our supported DBs. So, we resort to a bit of
12
12
  # DB-specific code.
13
13
  if Content.connection.kind_of? ActiveRecord::ConnectionAdapters::SQLiteAdapter
14
- date_func = "strftime('%Y') as year, strftime('%m') as month"
14
+ date_func = "strftime('%Y', published_at) as year, strftime('%m', published_at) as month"
15
15
  else
16
16
  date_func = "extract(year from published_at) as year,extract(month from published_at) as month"
17
17
  end
@@ -7,5 +7,5 @@ class Plugins::Sidebars::XmlController < Sidebars::ComponentPlugin
7
7
  setting :trackbacks, false, :input_type => :checkbox
8
8
 
9
9
  setting :format, 'rss20', :input_type => :radio,
10
- :choices => [["rss20", "RSS 2.0"], ["atom10", "Atom 1.0"], ["atom03", "Atom 0.3"]]
10
+ :choices => [["rss20", "RSS 2.0"], ["atom10", "Atom 1.0"]]
11
11
  end
@@ -55,7 +55,7 @@ This macro takes a number of parameters:
55
55
  imageurl = details['source']
56
56
  imagelink = flickrimage.url
57
57
 
58
- caption ||= sanitize(CGI.unescapeHTML(flickrimage.description))
58
+ caption ||= sanitize(CGI.unescapeHTML(flickrimage.description)) unless flickrimage.description.blank?
59
59
  title ||= flickrimage.title
60
60
  alt ||= title
61
61
 
@@ -1,7 +1,9 @@
1
1
  require 'net/http'
2
2
 
3
3
  begin
4
- Kernel.require 'sparkline'
4
+ Kernel.require 'sparklines'
5
+
6
+ Sparklines # this will throw an exception if the require failed.
5
7
 
6
8
  class Plugins::Textfilters::SparklineController < TextFilterPlugin::MacroPost
7
9
  plugin_public_action :plot
@@ -81,7 +83,6 @@ For other options, see the [Ruby Sparkline][] website.
81
83
  render :text => ''
82
84
  end
83
85
  end
84
-
85
86
  rescue LoadError
86
87
  # ignore load errors by not loading any of the library
87
88
  end
@@ -2,6 +2,12 @@ class Plugins::Textfilters::TextileController < TextFilterPlugin::Markup
2
2
  plugin_display_name "Textile"
3
3
  plugin_description 'Textile markup language'
4
4
 
5
+ def self.help_text
6
+ %{
7
+ See [_why's Textile reference](http://hobix.com/textile/).
8
+ }
9
+ end
10
+
5
11
  def self.filtertext(controller,content,text,params)
6
12
  RedCloth.new(text).to_html(:textile)
7
13
  end
@@ -0,0 +1,2 @@
1
+ blog = Mongrel::Rails.new('/blog',{})
2
+ uri "/blog", :handler => blog
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # WordPress 1.5x converter for typo by Patrick Lenz <patrick@lenz.sh>
4
+ # Updated to work with WordPress 2.0.x by Phillip Toland <toland@mac.com>
5
+ #
6
+ # See http://fiatdev.com/wp/2006/07/10/wordpress-to-typo-conversion-script/
7
+ # for more information.
8
+ #
9
+ # MAKE BACKUPS OF EVERYTHING BEFORE RUNNING THIS SCRIPT!
10
+ # THIS SCRIPT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
11
+
12
+ require File.dirname(__FILE__) + '/../../config/environment'
13
+ require 'application'
14
+ require 'optparse'
15
+
16
+ class WP2Migrate
17
+ attr_accessor :options
18
+
19
+ def initialize
20
+ self.options = {}
21
+ self.parse_options
22
+
23
+ WP2Migrate.execute_without_timestamps do
24
+ self.create_blog
25
+ self.convert_users
26
+ self.convert_categories unless self.options[:tags]
27
+ self.convert_entries
28
+ end
29
+ end
30
+
31
+
32
+ # Replaces <code></code> tags with <typo:code> and </typo:code>
33
+ # Also rewrites Markdown URLs with relative paths to a new base url
34
+ def replacements(str)
35
+ str.gsub!('<code', '<typo:code')
36
+ str.gsub!('</code>', '</typo:code>')
37
+ if self.options[:base_url]
38
+ str.gsub!(/\(\/([^\)]*)\)/,"(#{self.options[:base_url]}" +'\1)')
39
+ str.gsub!(/\[([^\]]*)\]: \/([^$])/, '[\1]:' +"#{self.options[:base_url]}"+'\2')
40
+ end
41
+ str
42
+ end
43
+
44
+ def create_blog
45
+ puts 'Creating Blog...'
46
+
47
+ blog = Blog.new
48
+
49
+ ActiveRecord::Base.connection.select_all(%{
50
+ SELECT
51
+ (CASE option_name
52
+ WHEN 'blogname' THEN 'blog_name'
53
+ WHEN 'blogdescription' THEN 'blog_subtitle'
54
+ WHEN 'siteurl' THEN 'canonical_server_url'
55
+ WHEN 'default_comment_status' THEN 'default_allow_comments'
56
+ WHEN 'default_ping_status' THEN 'default_allow_pings'
57
+ END) AS name,
58
+ option_value AS value
59
+ FROM `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_options`
60
+ WHERE option_name IN ('blogname', 'default_comment_status', 'default_ping_status', 'siteurl', 'blogdescription')
61
+ }).each do |pref|
62
+ if pref['name'] =~ /^default_allow/
63
+ pref['value'] = (pref['value'] == "open" ? 1 : 0)
64
+ end
65
+
66
+ blog[pref['name']] = pref['value']
67
+ end
68
+
69
+ blog['send_outbound_pings'] = false
70
+ blog['text_filter'] = self.options[:text_filter]
71
+ blog['comment_text_filter'] = self.options[:text_filter]
72
+
73
+ blog.save
74
+ end
75
+
76
+ def convert_users
77
+ # The SQL statement below is meant to capture all of the WordPress users
78
+ # who have permission to post content and not the users who just signed up
79
+ # to post a comment. There is some confusion in WP 2.0 about the user levels
80
+ # and user roles. I am currently using the user levels to select the
81
+ # appropriate users even though that is the "old way". If that is a problem,
82
+ # replace the second part of the where clause with:
83
+ # AND (usermeta.meta_key = 'wp_capabilities' AND INSTR(meta_value, 'subscriber') = 0)
84
+ # which does (mostly) the same thing using the new roles system.
85
+ wp_users = ActiveRecord::Base.connection.select_all(%{
86
+ SELECT
87
+ users.ID AS id,
88
+ user_login AS login,
89
+ user_email AS email,
90
+ display_name AS name
91
+ FROM `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_users` AS users,
92
+ `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_usermeta` AS usermeta
93
+ WHERE users.ID = usermeta.user_id
94
+ AND (usermeta.meta_key = 'wp_user_level' AND (meta_value > 0 AND meta_value < 10))
95
+ })
96
+
97
+ puts "Converting #{wp_users.size} users..."
98
+
99
+ wp_users.each do |user|
100
+ u = User.new user
101
+ u.id = user['id']
102
+ u.password = u.password_confirmation = 'password'
103
+ u.save
104
+ end
105
+ end
106
+
107
+ def convert_categories
108
+ wp_categories = ActiveRecord::Base.connection.select_all(%{
109
+ SELECT cat_name AS name
110
+ FROM `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_categories`
111
+ })
112
+
113
+ puts "Converting #{wp_categories.size} categories.."
114
+
115
+ wp_categories.each do |cat|
116
+ Category.create(cat) unless Category.find_by_name(cat['name'])
117
+ end
118
+ end
119
+
120
+ def convert_entries
121
+ wp_entries = ActiveRecord::Base.connection.select_all(%{
122
+ SELECT
123
+ `#{self.options[:wp_prefix]}_posts`.ID,
124
+ (CASE comment_status WHEN 'closed' THEN '0' ELSE '1' END) AS allow_comments,
125
+ (CASE ping_status WHEN 'closed' THEN '0' ELSE '1' END) AS allow_pings,
126
+ post_title AS title,
127
+ post_content AS body,
128
+ post_excerpt AS excerpt,
129
+ post_name AS permalink,
130
+ post_date AS created_at,
131
+ post_modified AS updated_at,
132
+ (CASE LENGTH(user_nicename) WHEN '0' THEN user_login ELSE user_nicename END) AS author,
133
+ (CASE post_status WHEN 'publish' THEN '1' ELSE '0' END) AS published,
134
+ post_author AS user_id,
135
+ post_status,
136
+ post_category
137
+ FROM `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_posts`, `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_users`
138
+ WHERE `#{self.options[:wp_prefix]}_users`.ID = `#{self.options[:wp_prefix]}_posts`.post_author
139
+ })
140
+
141
+ puts "Converting #{wp_entries.size} entries.."
142
+
143
+ wp_entries.each do |entry|
144
+ if entry['post_status'] == 'static'
145
+ a = Page.new
146
+ a.attributes = entry.reject { |k,v| k =~ /^(ID|post_category|body|post_status|permalink)/ }
147
+ a.name = entry['permalink']
148
+ a.created_at = entry['created_at']
149
+ a.updated_at = entry['updated_at']
150
+ else
151
+ a = Article.new
152
+ a.attributes = entry.reject { |k,v| k =~ /^(ID|post_category|body|post_status)/ }
153
+ a.created_at = entry['created_at']
154
+ a.created_at ||= DateTime.now
155
+ a.updated_at = entry['updated_at']
156
+ end
157
+
158
+ a.text_filter = TextFilter.find(:first, :conditions => [ 'name = ?', self.options[:text_filter] ] )
159
+
160
+ body = replacements(entry['body'])
161
+ more_index = body.index('<!--more-->')
162
+ if more_index
163
+ a.body = body[0...more_index]
164
+ a.extended = body[more_index + 11...body.length]
165
+ else
166
+ a.body = body
167
+ end
168
+ a.save
169
+
170
+ # Assign primary category
171
+ unless entry['post_category'].to_i.zero?
172
+ puts "Assign primary category for entry #{entry['ID']}"
173
+
174
+ ActiveRecord::Base.connection.select_all(%{
175
+ SELECT cat_name
176
+ FROM `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_categories`
177
+ WHERE cat_ID = '#{entry['post_category']}'
178
+ }).each do |c|
179
+ if self.options[:tags]
180
+ a.keywords = c['cat_name'].downcase || "";
181
+ else
182
+ a.categories.push_with_attributes(Category.find_by_name(c['cat_name']), :is_primary => 1) rescue nil
183
+ end
184
+ end
185
+ end unless a.instance_of?(Page)
186
+
187
+ # Fetch category assignments
188
+ puts "Assign remaining categories for entry #{entry['ID']}"
189
+ ActiveRecord::Base.connection.select_all(%{
190
+ SELECT cat_name
191
+ FROM `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_categories`, `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_post2cat`
192
+ WHERE post_id = #{entry['ID']}
193
+ AND `#{self.options[:wp_prefix]}_post2cat`.category_id = `#{self.options[:wp_prefix]}_categories`.cat_ID
194
+ }).each do |c|
195
+ if self.options[:tags]
196
+ a.keywords ||= ""
197
+ a.keywords << " " << c['cat_name'].gsub(/\s/,'-').downcase
198
+ else
199
+ a.categories.push_with_attributes(Category.find_by_name(c['cat_name']), :is_primary => 0)
200
+ end
201
+ end unless a.instance_of?(Page)
202
+
203
+ a.save if self.options[:tags] # force tags to save
204
+
205
+ # Fetch comments
206
+ ActiveRecord::Base.connection.select_all(%{
207
+ SELECT
208
+ comment_author AS author,
209
+ comment_author_email AS email,
210
+ comment_author_url AS url,
211
+ comment_content AS body,
212
+ comment_date AS created_at,
213
+ comment_author_IP AS ip
214
+ FROM `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_comments`
215
+ WHERE comment_post_ID = #{entry['ID']}
216
+ AND comment_type != 'trackback'
217
+ AND comment_approved = '1'
218
+ }).each do |c|
219
+ a.comments.create(c)
220
+ end unless a.instance_of?(Page)
221
+
222
+ # Fetch trackbacks
223
+ ActiveRecord::Base.connection.select_all(%{
224
+ SELECT
225
+ comment_author AS blog_name,
226
+ comment_author_url AS url,
227
+ comment_content AS excerpt,
228
+ comment_date AS created_at,
229
+ comment_author_IP AS ip
230
+ FROM `#{self.options[:wp_db]}`.`#{self.options[:wp_prefix]}_comments`
231
+ WHERE comment_post_ID = #{entry['ID']}
232
+ AND comment_type = 'trackback'
233
+ AND comment_approved = '1'
234
+ }).each do |c|
235
+ c['title'] = c['excerpt'].match("<(strong)>(.+?)</\\1>")[2] rescue c['blog_name']
236
+ a.trackbacks.create(c)
237
+ end unless a.instance_of?(Page)
238
+
239
+ # Typo internally sets the published_at timestamp so we are going to go
240
+ # behind its back to set them to what they should be.
241
+ ActiveRecord::Base.connection.execute(%{
242
+ UPDATE contents SET published_at = created_at
243
+ WHERE published_at IS NOT NULL
244
+ })
245
+ end
246
+ end
247
+
248
+ def parse_options
249
+ OptionParser.new do |opt|
250
+ opt.banner = "Usage: wordpress.rb [options]"
251
+
252
+ opt.on('--db DBNAME', String, 'WordPress database name.') { |d| self.options[:wp_db] = d }
253
+ opt.on('--prefix PREFIX', String, 'WordPress table prefix (defaults to \'wp\').') { |d| self.options[:wp_prefix] = d }
254
+ opt.on('--filter TEXTFILTER', String, 'Textfilter for imported articles (defaults to textile): eg \'markdown smartypants\'') { |d| self.options[:text_filter] = d }
255
+ opt.on('--tags', 'Convert categories to tags') { self.options[:tags] = true; }
256
+ opt.on('--base-url URL', String, 'Rewrite the base url for Markdown relative paths. (Trailing / required).' ) { |url| self.options[:base_url] = url }
257
+ opt.on_tail('-h', '--help', 'Show this message.') do
258
+ puts opt
259
+ exit
260
+ end
261
+
262
+ opt.parse!(ARGV)
263
+ end
264
+
265
+ unless self.options.include?(:wp_db)
266
+ puts "See wordpress.rb --help for help."
267
+ exit
268
+ end
269
+
270
+ unless self.options.include?(:wp_prefix)
271
+ self.options[:wp_prefix] = "wp"
272
+ end
273
+
274
+ unless self.options.include?(:text_filter)
275
+ self.options[:text_filter] = 'textile'
276
+ end
277
+ end
278
+
279
+ # This method allows to execute a block while deactivating timestamp
280
+ # updating.
281
+ def self.execute_without_timestamps
282
+ old_state = ActiveRecord::Base.record_timestamps
283
+ ActiveRecord::Base.record_timestamps = false
284
+
285
+ yield
286
+
287
+ ActiveRecord::Base.record_timestamps = old_state
288
+ end
289
+ end
290
+
291
+ WP2Migrate.new