Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 71
- Log:
Start of upgrade to Typo 4.0.0, the latest stable release since
2.6.0. Note test/mocks/themes/azure changes from a file to a
directory, so the file has been removed in this revision and
the directory will be added in the next revision.
- Author:
- adh
- Date:
- Mon Aug 07 22:18:11 +0100 2006
- Size:
- 10745 Bytes
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 |