├── LICENSE.txt
├── README.txt
├── data.inc.php
├── display_step00.database-settings.inc.php
├── display_step01.analysis_results.inc.php
├── display_step02.set-options.inc.php
├── display_step03.migrate.inc.php
├── drupaltowordpress-custom.sql
├── drupaltowordpress.php
├── fix_duplicate_aliases.php
├── fix_duplicate_terms.php
├── fix_terms_charlength.php
├── functions_database.php
├── functions_display.php
├── functions_utility.php
├── license.html
└── style.css
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2013 Another Cup of Coffee Limited
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | Drupal to WordPress database migration tool
2 | by Another Cup of Coffee Limited
3 |
4 | This tool performs the bulk of the migration from Drupal 6 to WordPress 3.5.
5 | It works pretty well with our own installations but you will likely need
6 | to make some tweaks, either to the code or to the migrated database. If you're
7 | not sure how to make the appropriate changes, Another Cup of Coffee Limited
8 | will be happy to do the work. Contact us at http://anothercoffee.net
9 |
10 |
11 | Installation and use:
12 | Copy folder to your web server and run the drupaltowordpress.php script on your browser. Follow the on-screen instructions.
13 |
14 |
15 | CAUTION:
16 | Make a backup of both your Drupal and WordPress databases before running this
17 | tool. USE IS ENTIRELY AT YOUR OWN RISK.
18 |
19 | First released 2013-05-26 by Anthony Lopez-Vito of Another Cup of Coffee Limited
20 | http://anothercoffee.net
21 |
22 | All code is released under The MIT License.
23 | Please see LICENSE.txt.
24 |
25 |
26 | Credits:
27 |
28 | * Scott Anderson of Room 34 Creative Services.
29 | The queries for migrating from Drupal to WordPress are based on a post
30 | by Room 34 in http://blog.room34.com/archives/4530
31 |
32 | * David Coveney of Interconnect IT Ltd (UK).
33 | I used UI elements of Interconnect IT's WordPress Search and Replace Tool
34 | as a starting point to create the in-house scripts on which this tool
35 | is based.
36 | http://interconnectit.com/products/search-and-replace-for-wordpress-databases/
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/data.inc.php:
--------------------------------------------------------------------------------
1 | In data.inc
';
14 | print "
";
15 | print_r($_POST);
16 | print '
';
17 | print '';
18 | */
19 |
20 | define("D2W_VERSION", "0.4");
21 |
22 | /*** DB details ***/
23 | // Wordpress
24 | $wp_settings_array['host'] = isset( $_POST[ 'wp_host' ] ) ? stripcslashes( $_POST[ 'wp_host' ] ) : 'localhost'; // normally localhost, but not necessarily.
25 | $wp_settings_array['user'] = isset( $_POST[ 'wp_user' ] ) ? stripcslashes( $_POST[ 'wp_user' ] ) : 'username'; // your db userid
26 | $wp_settings_array['pass'] = isset( $_POST[ 'wp_pass' ] ) ? stripcslashes( $_POST[ 'wp_pass' ] ) : 'password'; // your db password
27 | $wp_settings_array['data'] = isset( $_POST[ 'wp_data' ] ) ? stripcslashes( $_POST[ 'wp_data' ] ) : 'wordpress'; // your database
28 | $wp_settings_array['char'] = isset( $_POST[ 'wp_char' ] ) ? stripcslashes( $_POST[ 'wp_char' ] ) : ''; // your db charset
29 | // Drupal
30 | $d_settings_array['host'] = $wp_settings_array['host']; // normally localhost, but not necessarily.
31 | $d_settings_array['user'] = $wp_settings_array['user']; // your db userid
32 | $d_settings_array['pass'] = $wp_settings_array['pass']; // your db password
33 | $d_settings_array['data'] = isset( $_POST[ 'd_data' ] ) ? stripcslashes( $_POST[ 'd_data' ] ) : 'drupal'; // your database
34 | $d_settings_array['char'] = isset( $_POST[ 'd_char' ] ) ? stripcslashes( $_POST[ 'd_char' ] ) : ''; // your db charset
35 | /*
36 | $d_settings_array['host'] = isset( $_POST[ 'd_host' ] ) ? stripcslashes( $_POST[ 'd_host' ] ) : ''; // normally localhost, but not necessarily.
37 | $d_settings_array['data'] = isset( $_POST[ 'd_data' ] ) ? stripcslashes( $_POST[ 'd_data' ] ) : ''; // your database
38 | $d_settings_array['user'] = isset( $_POST[ 'd_user' ] ) ? stripcslashes( $_POST[ 'd_user' ] ) : ''; // your db userid
39 | $d_settings_array['pass'] = isset( $_POST[ 'd_pass' ] ) ? stripcslashes( $_POST[ 'd_pass' ] ) : ''; // your db password
40 | $d_settings_array['char'] = isset( $_POST[ 'd_char' ] ) ? stripcslashes( $_POST[ 'd_char' ] ) : ''; // your db charset
41 | */
42 |
43 | /**********************************************************************
44 | * START: room34.com queries for migrating FROM Drupal TO WordPress
45 | *
46 | */
47 |
48 | /* room34.com: Empty previous content from WordPress database. */
49 | define("QUERY_LIST_POSTS", "SELECT * FROM ".$wp_settings_array['data'].".wp_posts;");
50 |
51 | /* room34.com: Empty previous content from WordPress database. */
52 | define("QUERY_TRUNCATE_WP_TABLES", "TRUNCATE TABLE ".$wp_settings_array['data'].".wp_comments;".
53 | "TRUNCATE TABLE ".$wp_settings_array['data'].".wp_links;".
54 | "TRUNCATE TABLE ".$wp_settings_array['data'].".wp_postmeta;".
55 | "TRUNCATE TABLE ".$wp_settings_array['data'].".wp_posts;".
56 | "TRUNCATE TABLE ".$wp_settings_array['data'].".wp_term_relationships;".
57 | "TRUNCATE TABLE ".$wp_settings_array['data'].".wp_term_taxonomy;".
58 | "TRUNCATE TABLE ".$wp_settings_array['data'].".wp_terms;");
59 |
60 | /*
61 | * room34.com: If you're not bringing over multiple Drupal authors, comment out these lines and the other
62 | * author-related queries near the bottom of the script.
63 | * This assumes you're keeping the default admin user (user_id = 1) created during installation.
64 | */
65 | define("QUERY_DELETE_WP_AUTHORS", "DELETE FROM ".
66 | $wp_settings_array['data'].
67 | ".wp_users WHERE ID > 1; DELETE FROM ".
68 | $wp_settings_array['data'].
69 | ".wp_usermeta WHERE user_id > 1;");
70 |
71 |
72 | /*
73 | * room34.com: TAGS
74 | * Using REPLACE prevents script from breaking if Drupal contains duplicate terms.
75 | */
76 | define("QUERY_CREATE_WP_TAGS", "REPLACE INTO ".$wp_settings_array['data'].".wp_terms ".
77 | "(term_id, name, slug, term_group) ".
78 | "SELECT DISTINCT ".
79 | "d.tid, d.name, REPLACE(LOWER(d.name), ' ', '_'), 0 ".
80 | "FROM ".$d_settings_array['data'].".term_data d ".
81 | "WHERE (1);"); // This helps eliminate spam tags from import; uncomment if necessary.
82 | // AND LENGTH(d.name) < 50
83 |
84 | /* room34.com: POST/TAG RELATIONSHIPS */
85 | define("QUERY_SET_TERM_RELATIONSHIPS", "INSERT INTO ".$wp_settings_array['data'].".wp_term_relationships (object_id, term_taxonomy_id) ".
86 | "SELECT DISTINCT nid, tid FROM ".$d_settings_array['data'].".term_node;");
87 |
88 | /* room34.com: Update tag counts */
89 | define("QUERY_UPDATE_TAG_COUNTS", "UPDATE ".$wp_settings_array['data'].".wp_term_taxonomy tt ".
90 | "SET count = ( ".
91 | "SELECT COUNT(tr.object_id) ".
92 | "FROM ".$wp_settings_array['data'].".wp_term_relationships tr ".
93 | "WHERE tr.term_taxonomy_id = tt.term_taxonomy_id);");
94 |
95 | /*
96 | * room34.com: COMMENTS
97 | * Keeps unapproved comments hidden.
98 | * Incorporates change noted here: http://www.mikesmullin.com/development/migrate-convert-import-drupal-5-to-wordpress-27/#comment-32169
99 | *
100 | * ACCL: Amended to truncate Drupal's homepage field (varchar 255) to fit
101 | * Wordpress' comment_author_url field (varchar 200)
102 | *
103 | */
104 | define("QUERY_HIDE_ADD_COMMENTS", "INSERT INTO ".$wp_settings_array['data'].".wp_comments ".
105 | "(comment_ID, comment_post_ID, comment_date, comment_content, comment_parent, comment_author, ".
106 | "comment_author_email, comment_author_url, comment_approved) ".
107 | "SELECT DISTINCT ".
108 | "cid, nid, FROM_UNIXTIME(timestamp), comment, pid, name, ".
109 | "mail, SUBSTRING(homepage,1,200), ((status + 1) % 2) ".
110 | "FROM ".$d_settings_array['data'].".comments;");
111 |
112 | /* room34.com: Update comments count on wp_posts table. */
113 | define("QUERY_UPDATE_COMMENTS_COUNTS", "UPDATE ".$wp_settings_array['data'].".wp_posts ".
114 | "SET comment_count = ( ".
115 | "SELECT COUNT(comment_post_id) ".
116 | "FROM ".$wp_settings_array['data'].".wp_comments ".
117 | "WHERE ".$wp_settings_array['data'].".wp_posts.id = ".$wp_settings_array['data'].".wp_comments.comment_post_id);");
118 |
119 | /* room34.com: Fix images in post content; uncomment if you're moving files from "files" to "wp-content/uploads". */
120 | define("QUERY_UPDATE_FILEPATH", "UPDATE ".$wp_settings_array['data'].".wp_posts SET post_content = REPLACE(post_content, '\"/files/', '\"/wp-content/uploads/');");
121 |
122 | /* room34.com: Fix taxonomy; http://www.mikesmullin.com/development/migrate-convert-import-tindrupal6-5-to-wordpress-27/#comment-27140 */
123 | define("QUERY_FIX_TAXONOMY", "UPDATE IGNORE ".$wp_settings_array['data'].".wp_term_relationships, ".$wp_settings_array['data'].".wp_term_taxonomy ".
124 | "SET ".$wp_settings_array['data'].".wp_term_relationships.term_taxonomy_id = ".$wp_settings_array['data'].".wp_term_taxonomy.term_taxonomy_id ".
125 | "WHERE ".$wp_settings_array['data'].".wp_term_relationships.term_taxonomy_id = ".$wp_settings_array['data'].".wp_term_taxonomy.term_id;");
126 |
127 |
128 | /*
129 | * Stuff below needs more testing and customizatio
130 | */
131 |
132 | /* room34.com: AUTHORS */
133 | define("QUERY_ADD_AUTHORS", "INSERT IGNORE INTO ".$wp_settings_array['data'].".wp_users ".
134 | "(ID, user_login, user_pass, user_nicename, user_email, ".
135 | "user_registered, user_activation_key, user_status, display_name) ".
136 | "SELECT DISTINCT ".
137 | "u.uid, u.mail, NULL, u.name, u.mail, ".
138 | "FROM_UNIXTIME(created), '', 0, u.name ".
139 | "FROM ".$d_settings_array['data'].".users u ".
140 | "INNER JOIN ".$d_settings_array['data'].".users_roles r ".
141 | "USING (uid) ".
142 | "WHERE (1);"); // Uncomment and enter any email addresses you want to exclude below.
143 | // AND u.mail NOT IN ('test@example.com')
144 |
145 |
146 | /* room34.com: Assign author permissions.
147 | * Sets all authors to "author" by default; next section can selectively promote individual authors
148 | *
149 | * ACCL: Buidling from two separate queries for troubleshooting
150 | */
151 | define("QUERY_ASSIGN_AUTHOR_PERMISSIONS_A", "INSERT IGNORE INTO ".$wp_settings_array['data'].".wp_usermeta (user_id, meta_key, meta_value) ".
152 | "SELECT DISTINCT ".
153 | "u.uid, 'wp_capabilities', 'a:1:{s:6:\"author\";s:1:\"1\";}' ".
154 | "FROM ".$d_settings_array['data'].".users u ".
155 | "INNER JOIN ".$d_settings_array['data'].".users_roles r ".
156 | "USING (uid) ".
157 | "WHERE (1);"); // Uncomment and enter any email addresses you want to exclude below.
158 | // AND u.mail NOT IN ('test@example.com')
159 |
160 | define("QUERY_ASSIGN_AUTHOR_PERMISSIONS_B", "INSERT IGNORE INTO ".$wp_settings_array['data'].".wp_usermeta (user_id, meta_key, meta_value) ".
161 | "SELECT DISTINCT ".
162 | "u.uid, 'wp_user_level', '2' ".
163 | "FROM ".$d_settings_array['data'].".users u ".
164 | "INNER JOIN ".$d_settings_array['data'].".users_roles r ".
165 | "USING (uid) ".
166 | "WHERE (1);"); // Uncomment and enter any email addresses you want to exclude below.
167 | // AND u.mail NOT IN ('test@example.com')
168 |
169 | define("QUERY_ASSIGN_AUTHOR_PERMISSIONS", QUERY_ASSIGN_AUTHOR_PERMISSIONS_A.QUERY_ASSIGN_AUTHOR_PERMISSIONS_B);
170 |
171 |
172 | /*
173 | * room34.com: Change permissions for admins.
174 | * Add any specific user IDs to IN list to make them administrators.
175 | * User ID values are carried over from Drupal.
176 | */
177 | define("QUERY_ASSIGN_ADMIN_PERMISSIONS", "UPDATE ".$wp_settings_array['data'].".wp_usermeta ".
178 | "SET meta_value = 'a:1:{s:13:\"administrator\";s:1:\"1\";}' ".
179 | "WHERE user_id IN (1) AND meta_key = 'wp_capabilities'; ".
180 | "UPDATE ".$wp_settings_array['data'].".wp_usermeta ".
181 | "SET meta_value = '10' ".
182 | "WHERE user_id IN (1) AND meta_key = 'wp_user_level';");
183 |
184 | /* room34.com: Reassign post authorship. */
185 | define("QUERY_ASSIGN_POST_AUTHORSHIP", "UPDATE ".$wp_settings_array['data'].".wp_posts ".
186 | "SET post_author = NULL ".
187 | "WHERE post_author NOT IN (SELECT DISTINCT ID FROM ".$wp_settings_array['data'].".wp_users);");
188 |
189 | /*
190 | * room34.com: VIDEO - READ BELOW AND COMMENT OUT IF NOT APPLICABLE TO YOUR SITE
191 | * If your Drupal site uses the content_field_video table to store links to YouTube videos,
192 | * this query will insert the video URLs at the end of all relevant posts.
193 | * WordPress will automatically convert the video URLs to YouTube embed code.
194 | */
195 | /*
196 | define("QUERY_ADD_VIDEO_URLS", "UPDATE IGNORE ".$wp_settings_array['data'].".wp_posts p, ".$d_settings_array['data'].".content_field_video v ".
197 | "SET p.post_content = CONCAT_WS('\n',post_content,v.field_video_embed) ".
198 | "WHERE p.ID = v.nid;");
199 | */
200 |
201 | /*
202 | * room34.com: IMAGES - READ BELOW AND COMMENT OUT IF NOT APPLICABLE TO YOUR SITE
203 | * If your Drupal site uses the content_field_image table to store images associated with posts,
204 | * but not actually referenced in the content of the posts themselves, this query
205 | * will insert the images at the top of the post.
206 | * HTML/CSS NOTE: The code applies a "".$d_settings_array['data']."_image" class to the image and places it inside a
207 | * with the "".$d_settings_array['data']."_image_wrapper" class. Add CSS to your WordPress theme as appropriate to
208 | * handle styling of these elements. The tag as written assumes you'll be copying the
209 | * Drupal "files" directory into the root level of WordPress, NOT placing it inside the
210 | * "wp-content/uploads" directory. It also relies on a properly formatted tag.
211 | * Make changes as necessary before running this script!
212 | */
213 | /*
214 | define("QUERY_ADD_IMAGES", "UPDATE IGNORE ".$wp_settings_array['data'].".wp_posts p, ".$d_settings_array['data'].".content_field_image i, ".$d_settings_array['data'].".files f ".
215 | "SET p.post_content = ".
216 | "CONCAT( ".
217 | "CONCAT( ".
218 | "'
' ".
221 | "), ".
222 | "p.post_content ".
223 | ") ".
224 | "WHERE p.ID = i.nid ".
225 | "AND i.field_image_fid = f.fid ".
226 | "AND ( ".
227 | "f.filename LIKE '%.jpg' ".
228 | "OR f.filename LIKE '%.jpeg' ".
229 | "OR f.filename LIKE '%.png' ".
230 | "OR f.filename LIKE '%.gif');");
231 | */
232 |
233 | /*
234 | * room34.com: Fix post_name to remove paths.
235 | * If applicable; Drupal allows paths (i.e. slashes) in the dst field, but this breaks
236 | * WordPress URLs. If you have mod_rewrite turned on, stripping out the portion before
237 | * the final slash will allow old site links to work properly, even if the path before
238 | * the slash is different!
239 | */
240 | /*
241 | define("QUERY_FIX_POST_NAME", "UPDATE ".$wp_settings_array['data'].".wp_posts ".
242 | "SET post_name = ".
243 | "REVERSE(SUBSTRING(REVERSE(post_name),1,LOCATE('/',REVERSE(post_name))-1));");
244 | */
245 |
246 | /*
247 | * room34.com: Miscellaneous clean-up.
248 | * There may be some extraneous blank spaces in your Drupal posts; use these queries
249 | * or other similar ones to strip out the undesirable tags.
250 | */
251 | /*
252 | define("QUERY_STRIP_NBSP", "UPDATE ".$wp_settings_array['data'].".wp_posts SET post_content = REPLACE(post_content,'
','');");
253 | define("QUERY_STRIP_TAGS_01", "UPDATE ".$wp_settings_array['data'].".wp_posts SET post_content = REPLACE(post_content,'
','');");
254 | */
255 |
256 | /*
257 | * room34.com: NEW PAGES - READ BELOW AND COMMENT OUT IF NOT APPLICABLE TO YOUR SITE
258 | * MUST COME LAST IN THE SCRIPT AFTER ALL OTHER QUERIES!
259 | * If your site will contain new pages, you can set up the basic structure for them here.
260 | * Once the import is complete, go into the WordPress admin and copy content from the Drupal
261 | * pages (which are set to "pending" in a query above) into the appropriate new pages.
262 | */
263 | /*
264 | define("QUERY_SETUP_NEW_PAGE_STRUCTURE", "INSERT INTO ".$wp_settings_array['data'].".wp_posts ".
265 | "('post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', ".
266 | "'post_excerpt', 'post_status', 'comment_status', 'ping_status', 'post_password', ".
267 | "'post_name', 'to_ping', 'pinged', 'post_modified', 'post_modified_gmt', ".
268 | "'post_content_filtered', 'post_parent', 'guid', 'menu_order', 'post_type', ".
269 | "'post_mime_type', 'comment_count') ".
270 | "VALUES ".
271 | "(1, NOW(), NOW(), 'Page content goes here, or leave this value empty.', 'Page Title', ".
272 | "'', 'publish', 'closed', 'closed', '', ".
273 | "'slug-goes-here', '', '', NOW(), NOW(), ".
274 | "'', 0, 'http://full.url.to.page.goes.here', 1, 'page', '', 0);");
275 | */
276 |
277 | /* room34.com: Disable or enable comments */
278 | /*
279 | UPDATE ".$wp_settings_array['data'].".wp_posts p SET comment_status = 'closed', ping_status = 'closed' WHERE comment_status = 'open';
280 | UPDATE ".$wp_settings_array['data'].".wp_posts p SET comment_status = 'open', ping_status = 'open' WHERE comment_status = 'closed';
281 | */
282 |
283 | /*
284 | * END: room34.com queries for migrating FROM Drupal TO WordPress
285 | *
286 | **********************************************************************/
287 |
288 |
289 | /**********************************************************************
290 | * Additional work and research by ACCL
291 | *
292 | */
293 |
294 | /* Set site name, description and admin email */
295 | define("QUERY_SET_SITE_NAME", "UPDATE ".$wp_settings_array['data'].".wp_options ".
296 | "SET option_value = ( SELECT value FROM ".$d_settings_array['data'].".variable WHERE name='site_name') ".
297 | "WHERE option_name = 'blogname';");
298 |
299 | define("QUERY_SET_SITE_DESC", "UPDATE ".$wp_settings_array['data'].".wp_options ".
300 | "SET option_value = ( SELECT value FROM ".$d_settings_array['data'].".variable WHERE name='site_slogan') ".
301 | "WHERE option_name = 'blogdescription';");
302 |
303 | define("QUERY_SET_SITE_EMAIL", "UPDATE ".$wp_settings_array['data'].".wp_options ".
304 | "SET option_value = ( SELECT value FROM ".$d_settings_array['data'].".variable WHERE name='site_mail') ".
305 | "WHERE option_name = 'admin_email';");
306 |
307 | /******************************
308 | * Querying Drupal
309 | *
310 | */
311 | define("QUERY_DRUPAL_GET_POSTS", "SELECT DISTINCT ".
312 | "nid, FROM_UNIXTIME(created) post_date, title, type ".
313 | "FROM ".$d_settings_array['data'].".node;"); // Add more Drupal content types below if applicable.
314 |
315 | define("QUERY_DRUPAL_GET_NODE_TYPES", "SELECT DISTINCT type, name, description FROM ".$d_settings_array['data'].".node_type n "); // Add more Drupal content types below if applicable.
316 |
317 | define("QUERY_DRUPAL_GET_TERMS", "SELECT DISTINCT tid, name, REPLACE(LOWER(name), ' ', '_') slug, 0 ".
318 | "FROM ".$d_settings_array['data'].".term_data WHERE (1);");
319 |
320 | /******************************
321 | * Checks for common problems
322 | *
323 | */
324 |
325 | /*
326 | * Can't import duplicate terms into the WordPress wp_terms table
327 | */
328 | define("QUERY_DRUPAL_GET_DUPLICATE_TERMS", "SELECT tid, name, COUNT(*) c FROM ".$d_settings_array['data'].".term_data GROUP BY name HAVING c > 1;");
329 |
330 | /*
331 | * WordPress term name field is set 200 chars but Drupal's is term name is 255 chars
332 | */
333 | define("QUERY_DRUPAL_TERMS_CHARLENGTH", "SELECT tid, name FROM ".$d_settings_array['data'].".term_data WHERE CHAR_LENGTH(name) > 200;");
334 |
335 | /*
336 | * The node ID in Drupal's node table is used to create the post ID in WordPress' wp_posts table.
337 | *
338 | * The post name in WordPress' wp_posts table is created using either
339 | * (a) the url alias (dst field) in Drupal's url_alias table OR
340 | * (b) the node id (nid) in Drupal's node table IF there is no url alias
341 | *
342 | * If there are multiple Drupal aliases with the same node ID, we will end up trying to create multiple entries
343 | * into the WordPress wp_posts table with the same wp_posts ID. This will cause integrity constraint violation
344 | * errors since wp_posts ID is a unique primary key.
345 | *
346 | * To avoid this error, we need to check for duplicate aliases
347 | *
348 | * See buildQueryCreateWPPosts()
349 | */
350 | define("QUERY_DRUPAL_GET_DUPLICATE_ALIAS", "SELECT pid, src, COUNT(*) c FROM ".$d_settings_array['data'].".url_alias GROUP BY src HAVING c > 1;");
351 |
352 | ?>
353 |
354 |
--------------------------------------------------------------------------------
/display_step00.database-settings.inc.php:
--------------------------------------------------------------------------------
1 |
10 |
This tool performs the bulk of the migration from Drupal 6 to WordPress 3.5. It works pretty well with our own Drupal installations but you may need to make some tweaks to get things right for you, either to the code or to the migrated WordPress database. If you're not sure how to make the appropriate changes or would simply like someone else to do the work, please contact Another Cup of Coffee Limited and we'll be happy to provide a quotation.
11 |
12 |
CAUTION: Make a backup of both your Drupal and WordPress databases before running this tool. USE IS ENTIRELY AT YOUR OWN RISK.
$duplicate_terms_count duplicate terms. We can't import duplicate terms into WordPress. The migration will fail if these are to be included. Clicking Fix will solve this by appending the term's tid to create a unique term.
";
98 | ?>
99 |
100 |
101 |
102 |
103 |
Duplicate terms
104 |
105 |
name
106 |
count
107 |
108 |
109 |
110 | $row_val) {
112 | echo "
".$row_val['name']."
".$row_val['c']."
";
113 | }
114 | ?>
115 |
116 |
117 |
118 |
124 |
125 |
126 |
154 |
155 |
156 |
Term character length exceeded
$terms_charlength_exceeded_count terms exceed WordPress' 200 character length. The migration will fail if these are to be included. Clicking Fix will solve this by truncating the column to 200 characters. Warning: this will cause some data loss on the truncated columns.
";
167 | ?>
168 |
169 |
170 |
171 |
172 |
Term character length exceeded
173 |
174 |
tid
175 |
name
176 |
177 |
178 |
179 | $row_val) {
181 | echo "
".$row_val['tid']."
".$row_val['name']."
";
182 | }
183 | ?>
184 |
185 |
186 |
187 |
193 |
194 |
222 |
223 |
Duplicate aliases
$duplicate_aliases_count duplicate aliases found. Due to the way we build the WordPress post data, Drupal nodes with multiple url aliases will cause errors. More
";
233 | ?>
234 |
235 |
236 |
The node ID in Drupal's node table is used to create the post ID in WordPress' wp_posts table.
237 |
The post name in WordPress' wp_posts table is created using either
238 |
239 |
(a) the url alias (dst field) in Drupal's url_alias table OR
240 |
(b) the node id (nid) in Drupal's node table IF there is no url alias
241 |
242 |
If there are multiple Drupal aliases with the same node ID, we will end up trying to create multiple entries into the WordPress wp_posts table with the same wp_posts ID. This will cause integrity constraint violation errors since the ID field in wp_posts is a unique primary key.
243 |
To avoid this error, we need to check for duplicate aliases.
244 |
245 |
246 |
252 |
253 |
254 |
255 |
256 |
257 |
Duplicate aliases
258 |
259 |
src
260 |
count
261 |
262 |
263 | $row_val) {
265 | echo "
".$row_val['src']."
".$row_val['c']."
";
266 | }
267 | ?>
268 |
269 |
270 |
271 |
277 |
278 |
306 |
307 |
--------------------------------------------------------------------------------
/display_step02.set-options.inc.php:
--------------------------------------------------------------------------------
1 |
23 |
94 |
95 |
96 |
124 |
125 | Please select which Drupal content types will be converted into WordPress posts. The unselected types will be converted in to pages.
";
145 | }
146 | ?>
--------------------------------------------------------------------------------
/display_step03.migrate.inc.php:
--------------------------------------------------------------------------------
1 | Done";
9 | showHTMLFooter();
10 | }
11 | ?>
12 |
--------------------------------------------------------------------------------
/drupaltowordpress-custom.sql:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Drupal to WordPress database migration tool
3 | * by Another Cup of Coffee Limited
4 | *
5 | * Version 0.2
6 | *
7 | * This script is based on the Drupal to WordPress Migration tool
8 | * drupaltowordpress-d6w35 version 3 by Another Cup of Coffee Limited.
9 | * It allows you to run a Drupal to WordPress migration without the user interface.
10 | *
11 | * This was a custom script written for the specific needs of a client but may be
12 | * useful for other migrations. I've stripped out any identifying information but
13 | * left some generic data to provide an example.
14 | *
15 | * Migration options are set directly in this script.
16 | *
17 | *
18 | * CAUTION:
19 | * Make a backup of both your Drupal and WordPress databases before running this
20 | * tool. USE IS ENTIRELY AT YOUR OWN RISK.
21 | *
22 | * First released by Anthony Lopez-Vito of Another Cup of Coffee Limited
23 | * http://anothercoffee.net
24 | *
25 | * All code is released under The MIT License.
26 | * Please see LICENSE.txt.
27 | *
28 | * Credits: Please see README.txt for credits
29 | *
30 | *******************************************************************************/
31 |
32 |
33 | /********************
34 | * Clear out WP tables.
35 | */
36 | TRUNCATE TABLE wordpressdb.wp_comments;
37 | TRUNCATE TABLE wordpressdb.wp_links;
38 | TRUNCATE TABLE wordpressdb.wp_postmeta;
39 | TRUNCATE TABLE wordpressdb.wp_posts;
40 | TRUNCATE TABLE wordpressdb.wp_term_relationships;
41 | TRUNCATE TABLE wordpressdb.wp_term_taxonomy;
42 | TRUNCATE TABLE wordpressdb.wp_terms;
43 | TRUNCATE TABLE wordpressdb.wp_users;
44 | /*
45 | * For some installations, we make changes to the wp_usermeta table
46 | TRUNCATE TABLE wordpressdb.wp_usermeta;
47 | */
48 |
49 | /********************
50 | * Clear out working tables.
51 | *
52 | * These may have been created during a previous run of the migration queries.
53 | */
54 | DROP TABLE IF EXISTS drupaldb.acc_duplicates;
55 | DROP TABLE IF EXISTS drupaldb.acc_news_terms;
56 | DROP TABLE IF EXISTS drupaldb.acc_tags_terms;
57 | DROP TABLE IF EXISTS drupaldb.acc_wp_tags;
58 | DROP TABLE IF EXISTS drupaldb.acc_users_post_count;
59 | DROP TABLE IF EXISTS drupaldb.acc_users_comment_count;
60 | DROP TABLE IF EXISTS drupaldb.acc_users_with_content;
61 | DROP TABLE IF EXISTS drupaldb.acc_users_post_count;
62 |
63 |
64 | /********************
65 | * Delete unwanted vocabularies and their associated terms.
66 | */
67 |
68 | /* Delete unwanted vocabularies */
69 | DELETE FROM drupaldb.vocabulary WHERE vid IN (5, 7, 8, 38, 40);
70 |
71 | /* Delete terms associated with unwanted vocabularies; keep 38.
72 | * Sometimes you might want to keep some terms of unwated
73 | * vocabularies to convert into WordPress tags
74 | */
75 | DELETE FROM drupaldb.term_data WHERE vid IN (5, 7, 8, 40);
76 |
77 |
78 | /********************
79 | * Merge terms.
80 | *
81 | * You may want to merge terms. In this case, we are merging vid 38
82 | * to the tag vocabulary terms (vid 2).
83 | *
84 | * We will need to deal with duplicates. For example, in the Drupal
85 | * installation, 'term_a' could appear in both vid 2 and vid 38. This
86 | * will cause a problem when exporting to WordPress since we can't have
87 | * duplicate terms.
88 | */
89 |
90 | /* Create working tables for tables both term groups.
91 | * In this case, vid 38 is a vocabulary called 'News' and
92 | * vid 2 is a vocabulary called 'Tags'.
93 | */
94 | CREATE TABLE drupaldb.acc_news_terms AS SELECT tid, vid, name FROM drupaldb.term_data WHERE vid=38;
95 | CREATE TABLE drupaldb.acc_tags_terms AS SELECT tid, vid, name FROM drupaldb.term_data WHERE vid=2;
96 |
97 | /* Create table from duplicates */
98 | CREATE TABLE drupaldb.acc_duplicates AS
99 | SELECT t.tid tag_tid,
100 | n.tid news_tid,
101 | t.vid tag_vid,
102 | n.vid news_vid,
103 | t.name
104 | FROM drupaldb.acc_tags_terms AS t
105 | INNER JOIN (drupaldb.acc_news_terms AS n)
106 | ON n.name=t.name;
107 |
108 | /* Append string to News terms duplicates so they won't clash during migration.
109 | * Here we used a fixed string but this won't work if you have more than two
110 | * terms with the same name. Better to generate a unique number. For example, using
111 | * the tid would make it unique since these are unique primary keys.
112 | */
113 | UPDATE drupaldb.term_data
114 | SET name=CONCAT(name, '_01')
115 | WHERE tid IN (SELECT news_tid FROM drupaldb.acc_duplicates);
116 |
117 | /* Convert News terms to Tags */
118 | UPDATE drupaldb.term_data SET vid=2 WHERE vid=38;
119 |
120 |
121 | /********************
122 | * Create a table of tags.
123 | *
124 | * Exclude terms from vocabularies that we might later
125 | * convert into categories. See stage below where we create categories
126 | * and sub-categories.
127 | */
128 | CREATE TABLE drupaldb.acc_wp_tags AS
129 | SELECT
130 | tid,
131 | vid,
132 | name
133 | FROM drupaldb.term_data
134 | WHERE vid NOT IN (37, 36, 35);
135 |
136 |
137 | /********************
138 | * Create the tags in the WordPress database
139 | */
140 |
141 | /* Add the tags to WordPress.
142 | *
143 | * A clean WordPress database will have term_id=1 for Uncategorized.
144 | * Use REPLACE as this may conflict with a Drupal tid
145 | */
146 |
147 | /* ASSUMPTION:
148 | * Assuming that this point the Drupal term_data table
149 | * has been cleaned of any duplicate names as any
150 | * duplicates will be lost.
151 | */
152 | REPLACE INTO wordpressdb.wp_terms (term_id, name, slug, term_group)
153 | SELECT
154 | d.tid,
155 | d.name,
156 | REPLACE(LOWER(d.name), ' ', '_'),
157 | d.vid
158 | FROM drupaldb.term_data d WHERE d.tid IN (
159 | SELECT t.tid FROM drupaldb.acc_wp_tags t
160 | );
161 |
162 | /* Convert these Drupal terms into tags */
163 | REPLACE INTO wordpressdb.wp_term_taxonomy (
164 | term_taxonomy_id,
165 | term_id,
166 | taxonomy,
167 | description,
168 | parent)
169 | SELECT DISTINCT
170 | d.tid,
171 | d.tid 'term_id',
172 | 'post_tag', /* This string makes them WordPress tags */
173 | d.description 'description',
174 | 0 /* In this case, we don't give tags a parent */
175 | FROM drupaldb.term_data d
176 | WHERE d.tid IN (SELECT t.tid FROM drupaldb.acc_wp_tags t);
177 |
178 |
179 | /********************
180 | * Create the categories and sub-categories in the WordPress database.
181 | *
182 | * This may be unnecessary depending on your setup.
183 | */
184 |
185 | /* Add terms associated with a Drupal vocabulary into WordPress.
186 | *
187 | * Note that in this case, these are the same vids that we
188 | * excluded from the tag table above.
189 | */
190 | REPLACE INTO wordpressdb.wp_terms (term_id, name, slug, term_group)
191 | SELECT DISTINCT
192 | d.tid,
193 | d.name,
194 | REPLACE(LOWER(d.name), ' ', '_'),
195 | d.vid
196 | FROM drupaldb.term_data d
197 | WHERE d.vid IN (37, 36, 35);
198 |
199 | /* Convert these Drupal terms into sub-categories by setting parent */
200 | REPLACE INTO wordpressdb.wp_term_taxonomy (
201 | term_taxonomy_id,
202 | term_id,
203 | taxonomy,
204 | description,
205 | parent)
206 | SELECT DISTINCT
207 | d.tid,
208 | d.tid 'term_id',
209 | 'category',
210 | d.description 'description',
211 | d.vid
212 | FROM drupaldb.term_data d
213 | WHERE d.vid IN (37, 36, 35);
214 |
215 | /* Add vocabularies to the WordPress terms table.
216 | *
217 | * No need to set term_id as vocabilaries are not
218 | * directly associated with posts
219 | */
220 | INSERT INTO wordpressdb.wp_terms (name, slug, term_group)
221 | SELECT DISTINCT
222 | v.name,
223 | REPLACE(LOWER(v.name), ' ', '_'),
224 | v.vid
225 | FROM drupaldb.vocabulary v
226 | WHERE vid IN (37, 36, 35);
227 |
228 | /* Insert Drupal vocabularies as WordPress categories */
229 | INSERT INTO wordpressdb.wp_term_taxonomy (
230 | term_id,
231 | taxonomy,
232 | description,
233 | parent,
234 | count)
235 | SELECT DISTINCT
236 | v.vid,
237 | 'category', /* This string makes them WordPress categories */
238 | v.description,
239 | v.vid,
240 | 0
241 | FROM drupaldb.vocabulary v
242 | WHERE vid IN (37, 36, 35);
243 |
244 | /* Update term groups and parents.
245 | *
246 | * Before continuing with this step, we need to manually inspect the table for the
247 | * term_id for the parents inserted above. In this case, vids 37, 36, 35 were inserted
248 | * as into the wp_term_taxonomy table as term_ids 7517, 7518 and 7519. We will use them
249 | * as the parents for their respective terms. i.e. terms that formerly belonged
250 | * to the Drupal vocabulary ID 37 would now belong to the WordPress parent category 7519.
251 | */
252 | UPDATE wordpressdb.wp_terms SET term_group=7519 WHERE term_group=37;
253 | UPDATE wordpressdb.wp_terms SET term_group=7518 WHERE term_group=36;
254 | UPDATE wordpressdb.wp_terms SET term_group=7517 WHERE term_group=35;
255 |
256 | UPDATE wordpressdb.wp_term_taxonomy SET parent=7519 WHERE parent=37;
257 | UPDATE wordpressdb.wp_term_taxonomy SET parent=7518 WHERE parent=36;
258 | UPDATE wordpressdb.wp_term_taxonomy SET parent=7517 WHERE parent=35;
259 |
260 | UPDATE wordpressdb.wp_term_taxonomy SET term_id=7519 WHERE term_taxonomy_id=7519;
261 | UPDATE wordpressdb.wp_term_taxonomy SET term_id=7518 WHERE term_taxonomy_id=7518;
262 | UPDATE wordpressdb.wp_term_taxonomy SET term_id=7517 WHERE term_taxonomy_id=7517;
263 |
264 |
265 | /********************
266 | * Re-insert the Uncategorized term replaced previously.
267 | *
268 | * We may have replaced or deleted the Uncategorized category
269 | * during an earlier query. Re-insert it if you want an
270 | * Uncategorized category.
271 | */
272 | INSERT INTO wordpressdb.wp_terms (name, slug, term_group)
273 | VALUES ('Uncategorized', 'uncategorized', 0);
274 | INSERT INTO wordpressdb.wp_term_taxonomy (
275 | term_taxonomy_id,
276 | term_id,
277 | taxonomy,
278 | description,
279 | parent,
280 | count)
281 | SELECT DISTINCT
282 | t.term_id,
283 | t.term_id,
284 | 'category',
285 | t.name,
286 | 0,
287 | 0
288 | FROM wordpressdb.wp_terms t
289 | WHERE t.slug='uncategorized';
290 |
291 |
292 | /********************
293 | * Create WP Posts from Drupal nodes
294 | */
295 | REPLACE INTO wordpressdb.wp_posts (
296 | id,
297 | post_author,
298 | post_date,
299 | post_content,
300 | post_title,
301 | post_excerpt,
302 | post_name,
303 | post_modified,
304 | post_type,
305 | post_status,
306 | to_ping,
307 | pinged,
308 | post_content_filtered)
309 | SELECT DISTINCT
310 | n.nid 'id',
311 | n.uid 'post_author',
312 | DATE_ADD(FROM_UNIXTIME(0), interval n.created second) 'post_date',
313 | r.body 'post_content',
314 | n.title 'post_title',
315 | r.teaser 'post_excerpt',
316 | IF(a.dst IS NULL,n.nid, SUBSTRING_INDEX(a.dst, '/', -1)) 'post_name',
317 | DATE_ADD(FROM_UNIXTIME(0), interval n.changed second) 'post_modified',
318 | n.type 'post_type',
319 | IF(n.status = 1, 'publish', 'private') 'post_status',
320 | ' ',
321 | ' ',
322 | ' '
323 | FROM drupaldb.node n
324 | INNER JOIN drupaldb.node_revisions r USING(vid)
325 | LEFT OUTER JOIN drupaldb.url_alias a
326 | ON a.src = CONCAT('node/', n.nid)
327 | WHERE n.type IN (
328 | /* List the content types you want to migrate */
329 | 'page',
330 | 'story',
331 | 'blog',
332 | 'video1',
333 | 'forum',
334 | 'comment');
335 |
336 | /* Set the content types that should be converted into 'posts' */
337 | UPDATE wordpressdb.wp_posts SET post_type = 'post'
338 | WHERE post_type IN (
339 | 'page',
340 | 'story',
341 | 'blog',
342 | 'video1',
343 | 'forum',
344 | 'comment');
345 |
346 | /* The rest of the content types are converted into pages */
347 | UPDATE wordpressdb.wp_posts SET post_type = 'page' WHERE post_type NOT IN ('post');
348 |
349 |
350 | /********************
351 | * Housekeeping queries for terms
352 | */
353 |
354 | /* Associate posts with terms */
355 | INSERT INTO wordpressdb.wp_term_relationships (
356 | object_id,
357 | term_taxonomy_id)
358 | SELECT DISTINCT nid, tid FROM drupaldb.term_node;
359 |
360 | /* Update tag counts */
361 | UPDATE wordpressdb.wp_term_taxonomy tt
362 | SET count = ( SELECT COUNT(tr.object_id)
363 | FROM wordpressdb.wp_term_relationships tr
364 | WHERE tr.term_taxonomy_id = tt.term_taxonomy_id);
365 |
366 | /* Fix taxonomy
367 | * Found in room34.com queries: Fix taxonomy
368 | * http://www.mikesmullin.com/development/migrate-convert-import-drupal-5-to-wordpress-27/#comment-27140
369 | *
370 | * IS THIS NECESSARY?
371 |
372 | UPDATE IGNORE wordpressdb.wp_term_relationships, wordpressdb.wp_term_taxonomy
373 | SET wordpressdb.wp_term_relationships.term_taxonomy_id = wordpressdb.wp_term_taxonomy.term_taxonomy_id
374 | WHERE wordpressdb.wp_term_relationships.term_taxonomy_id = wordpressdb.wp_term_taxonomy.term_id;
375 | */
376 |
377 | /* Set default category.
378 | *
379 | * Manually look in the database for the term_id of the category you want to set as
380 | * the default category.
381 | */
382 | UPDATE wordpressdb.wp_options SET option_value='7520' WHERE option_name='default_category';
383 | UPDATE wordpressdb.wp_term_taxonomy SET taxonomy='category' WHERE term_id=7520;
384 |
385 |
386 | /********************
387 | * Migrate comments
388 | */
389 | REPLACE INTO wordpressdb.wp_comments (
390 | comment_ID,
391 | comment_post_ID,
392 | comment_date,
393 | comment_content,
394 | comment_parent,
395 | comment_author,
396 | comment_author_email,
397 | comment_author_url,
398 | comment_approved)
399 | SELECT DISTINCT
400 | cid,
401 | nid,
402 | FROM_UNIXTIME(timestamp),
403 | comment,
404 | pid,
405 | name,
406 | mail,
407 | SUBSTRING(homepage,1,200),
408 | ((status + 1) % 2) FROM drupaldb.comments;
409 |
410 | /* Update comment counts */
411 | UPDATE wordpressdb.wp_posts
412 | SET comment_count = ( SELECT COUNT(comment_post_id)
413 | FROM wordpressdb.wp_comments
414 | WHERE wordpressdb.wp_posts.id = wordpressdb.wp_comments.comment_post_id);
415 |
416 |
417 | /********************
418 | * Migrate Drupal Authors into WordPress
419 | *
420 | * In this case we are migrating only users who have created a post.
421 | */
422 |
423 | /* Delete all WP Authors except for admin */
424 | DELETE FROM wordpressdb.wp_users WHERE ID > 1;
425 | DELETE FROM wordpressdb.wp_usermeta WHERE user_id > 1;
426 |
427 | /* Set Drupal's admin password to a known value.
428 | *
429 | * This avoids hassles with trying to reset the password on
430 | * the new WordPress installation.
431 | *
432 | * UPDATE drupaldb.users set pass=md5('password') where uid = 1;
433 | *
434 | */
435 |
436 | /* Create table of users who have created a post */
437 | CREATE TABLE drupaldb.acc_users_post_count AS
438 | SELECT
439 | u.uid,
440 | u.name,
441 | u.mail,
442 | count(n.uid) node_count
443 | FROM drupaldb.node n
444 | INNER JOIN drupaldb.users u on n.uid = u.uid
445 | WHERE n.type IN (
446 | /* List the post types we migrated earlier */
447 | 'page',
448 | 'story',
449 | 'blog',
450 | 'video1',
451 | 'forum',
452 | 'comment')
453 | GROUP BY u.uid
454 | ORDER BY node_count;
455 |
456 | /* Add authors using table of users who have created a post */
457 | INSERT IGNORE INTO wordpressdb.wp_users (
458 | ID,
459 | user_login,
460 | user_pass,
461 | user_nicename,
462 | user_email,
463 | user_registered,
464 | user_activation_key,
465 | user_status,
466 | display_name)
467 | SELECT DISTINCT
468 | u.uid,
469 | REPLACE(LOWER(u.name), ' ', '_'),
470 | u.pass,
471 | u.name,
472 | u.mail,
473 | FROM_UNIXTIME(created),
474 | '',
475 | 0,
476 | u.name
477 | FROM drupaldb.users u
478 | WHERE u.uid IN (SELECT uid FROM drupaldb.acc_users_post_count);
479 |
480 | /* Sets all authors to "author" by default; next section can selectively promote individual authors */
481 | INSERT IGNORE INTO wordpressdb.wp_usermeta (
482 | user_id,
483 | meta_key,
484 | meta_value)
485 | SELECT DISTINCT
486 | u.uid,
487 | 'wp_capabilities',
488 | 'a:1:{s:6:"author";s:1:"1";}'
489 | FROM drupaldb.users u
490 | WHERE u.uid IN (SELECT uid FROM drupaldb.acc_users_post_count);
491 |
492 | INSERT IGNORE INTO wordpressdb.wp_usermeta (
493 | user_id,
494 | meta_key,
495 | meta_value)
496 | SELECT DISTINCT
497 | u.uid,
498 | 'wp_user_level',
499 | '2'
500 | FROM drupaldb.users u
501 | WHERE u.uid IN (SELECT uid FROM drupaldb.acc_users_post_count);
502 |
503 | /* Reassign post authorship to admin for posts have no author */
504 | UPDATE wordpressdb.wp_posts
505 | SET post_author = 1
506 | WHERE post_author NOT IN (SELECT DISTINCT ID FROM wordpressdb.wp_users);
507 |
508 |
509 | /********************
510 | * Housekeeping for WordPress options
511 | */
512 |
513 | /* Update filepath */
514 | UPDATE wordpressdb.wp_posts SET post_content = REPLACE(post_content, '"/files/', '"/wp-content/uploads/');
515 |
516 | /* Set site name */
517 | UPDATE wordpressdb.wp_options SET option_value = ( SELECT value FROM drupaldb.variable WHERE name='site_name') WHERE option_name = 'blogname';
518 |
519 | /* Set site description */
520 | UPDATE wordpressdb.wp_options SET option_value = ( SELECT value FROM drupaldb.variable WHERE name='site_slogan') WHERE option_name = 'blogdescription';
521 |
522 | /* Set site email */
523 | UPDATE wordpressdb.wp_options SET option_value = ( SELECT value FROM drupaldb.variable WHERE name='site_mail') WHERE option_name = 'admin_email';
524 |
525 | /* Set permalink structure */
526 | UPDATE wordpressdb.wp_options SET option_value = '/%postname%/' WHERE option_name = 'permalink_structure';
527 |
528 |
529 |
530 | /********************
531 | * Create URL redirects table
532 | *
533 | * This table will not be used for the migration but may be useful if
534 | * you need to manually create redirects from Drupal aliases
535 | */
536 |
537 | DROP TABLE IF EXISTS drupaldb.acc_redirects;
538 | CREATE TABLE drupaldb.acc_redirects AS
539 | SELECT
540 | CONCAT('drupaldb/',
541 | IF(a.dst IS NULL,
542 | CONCAT('node/', n.nid),
543 | a.dst
544 | )
545 | ) 'old_url',
546 | IF(a.dst IS NULL,n.nid, SUBSTRING_INDEX(a.dst, '/', -1)) 'new_url',
547 | '301' redirect_code
548 | FROM drupaldb.node n
549 | INNER JOIN drupaldb.node_revisions r USING(vid)
550 | LEFT OUTER JOIN drupaldb.url_alias a
551 | ON a.src = CONCAT('node/', n.nid)
552 | WHERE n.type IN (
553 | /* List the post types we migrated earlier */
554 | 'page',
555 | 'story',
556 | 'blog',
557 | 'video1',
558 | 'forum',
559 | 'comment');
560 |
561 |
562 |
563 | /********************
564 | * Run additional query to import users who have commented
565 | * but haven't created any of the selected content types
566 | *
567 | * Running this will throw errors if you haven't manually
568 | * copied over required tables and renamed copies
569 | * to the tables names below.
570 | *
571 | * Tables requred for these queries:
572 | * acc_users_with_comments: empty copy of wp_users
573 | * acc_users_add_commenters: empty copy of wp_users
574 | * acc_wp_users: copy of wp_users from wordpress database containing users
575 | *
576 | */
577 |
578 | /* Create table of users who have created a comment */
579 | CREATE TABLE drupaldb.acc_users_comment_count AS
580 | SELECT
581 | u.uid,
582 | u.name,
583 | count(c.uid) comment_count
584 | FROM drupaldb.comments c
585 | INNER JOIN drupaldb.users u on c.uid = u.uid
586 | GROUP BY u.uid;
587 |
588 | INSERT IGNORE INTO drupaldb.acc_users_with_comments (
589 | ID,
590 | user_login,
591 | user_pass,
592 | user_nicename,
593 | user_email,
594 | user_registered,
595 | user_activation_key,
596 | user_status,
597 | display_name)
598 | SELECT DISTINCT
599 | u.uid,
600 | REPLACE(LOWER(u.name), ' ', '_'),
601 | u.pass,
602 | u.name,
603 | u.mail,
604 | FROM_UNIXTIME(created),
605 | '',
606 | 0,
607 | u.name
608 | FROM drupaldb.users u
609 | WHERE u.uid IN (SELECT uid FROM drupaldb.acc_users_comment_count);
610 |
611 | /* Build a table of users who have commented but
612 | * not already added to WordPress' wp_users */
613 | INSERT IGNORE INTO drupaldb.acc_users_add_commenters (
614 | ID,
615 | user_login,
616 | user_pass,
617 | user_nicename,
618 | user_email,
619 | user_registered,
620 | user_activation_key,
621 | user_status,
622 | display_name)
623 | SELECT DISTINCT
624 | u.ID,
625 | u.user_login,
626 | u.user_pass,
627 | u.user_nicename,
628 | u.user_email,
629 | u.user_registered,
630 | '',
631 | 0,
632 | u.display_name
633 | FROM drupaldb.acc_users_with_comments u
634 | WHERE u.ID NOT IN (SELECT ID FROM drupaldb.acc_wp_users);
635 |
636 | /* Combine the tables
637 | * Remember to copy wp_users back into wordpress database
638 | */
639 | INSERT IGNORE
640 | INTO drupaldb.acc_wp_users
641 | SELECT *
642 | FROM drupaldb.acc_users_add_commenters;
643 |
644 |
645 | /********************
646 | * Additional customisations
647 | *
648 | * --- ERROR: "You do not have sufficient permissions to access this page" ---
649 | *
650 | * If you receive this error after logging in to your new WordPress installation, it's possible that the
651 | * database prefix on your new WordPress site is not set correctly. This may happen if, for example, you used
652 | * a local WordPress installation to run the migration before setting up on your live WordPress installation.
653 | *
654 | * Try running one of the queries below.
655 | *
656 | * Sources:
657 | * (1) http://wordpress.org/support/topic/you-do-not-have-sufficient-permissions-to-access-this-page-98
658 | * (2) http://stackoverflow.com/questions/13815461/you-do-not-have-sufficient-permissions-to-access-this-page-without-any-change
659 | *
660 | * OPTION 1
661 | * UPDATE wp_new_usermeta SET meta_key = REPLACE(meta_key,'oldprefix_','newprefix_');
662 | * UPDATE wp_new_options SET option_name = REPLACE(option_name,'oldprefix_','newprefix_');
663 | *
664 | * OPTION 2
665 | * update wp_new_usermeta set meta_key = 'newprefix_usermeta' where meta_key = 'wp_capabilities';
666 | * update wp_new_usermeta set meta_key = 'newprefix_user_level' where meta_key = 'wp_user_level';
667 | * update wp_new_usermeta set meta_key = 'newprefix_autosave_draft_ids' where meta_key = 'wp_autosave_draft_ids';
668 | * update wp_new_options set option_name = 'newprefix_user_roles' where option_name = 'wp_user_roles';
669 | *
670 | *
671 | * --- Incorrect domain in link URLs ---
672 | *
673 | * WordPress stores the domains in the database. If you performed the migration on a local or development server,
674 | * there's a good chance that the links will be incorrect after migrating to your live server. Use the Interconnect IT
675 | * utility to run a search and replace on your database. This will also correct changed database prefixes.
676 | *
677 | * https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
678 | *
679 | *
680 | * END
681 | *
682 | ********************/
--------------------------------------------------------------------------------
/drupaltowordpress.php:
--------------------------------------------------------------------------------
1 | 0 ) {
34 | if( !testDatabaseConnection($wp_settings_array, $errors) ) {
35 | $step = 4;
36 | }
37 | }
38 |
39 | switch ( $step ) {
40 | case 0:
41 | include "display_step00.database-settings.inc.php";
42 | displayConnectionSettingsPage($wp_settings_array, $d_settings_array);
43 | break;
44 | case 1:
45 | include "display_step01.analysis_results.inc.php";
46 | displayAnalysisResultsPage($wp_settings_array, $d_settings_array);
47 | break;
48 | case 2:
49 | include "display_step02.set-options.inc.php";
50 | displaySetOptionsPage($wp_settings_array, $d_settings_array);
51 | break;
52 | case 3:
53 | $options_array = array();
54 | if(isset($_POST['formDeleteAuthors']) &&
55 | $_POST['formDeleteAuthors'] == 'Yes') {
56 | $options_array['deleteAuthors'] = $_POST['formDeleteAuthors'];
57 | }
58 | if(isset($_POST['formFilePath'])) {
59 | $options_array['filePath'] = $_POST['formFilePath'];
60 | }
61 | if(isset($_POST['formContentTypes'])) {
62 | $options_array['formContentTypes']=$_POST['formContentTypes'];
63 | }
64 | if(isset($_POST['formTerms'])) {
65 | $options_array['formTerms']=$_POST['formTerms'];
66 | }
67 | if(isset($_POST['formDefaultCategory'])) {
68 | $options_array['formDefaultCategory']=$_POST['formDefaultCategory'];
69 | }
70 | if(isset($_POST['formPermalinkStructure'])) {
71 | $options_array['formPermalinkStructure']=$_POST['formPermalinkStructure'];
72 | }
73 |
74 | $errors = migrate($wp_settings_array, $d_settings_array, $options_array);
75 | include "display_step03.migrate.inc.php";
76 | displayResultsPage($wp_settings_array, $d_settings_array, $errors);
77 | break;
78 | case 4:
79 | default:
80 | /*
81 | print "
";
82 | print_r($_POST);
83 | print '
';
84 | print '';
85 | print "
";
86 | print_r($options_array);
87 | print '
';
88 | print '';
89 | */
90 | showErrorPage($errors);
91 | break;
92 | }
93 |
94 | ?>
95 |
96 |
--------------------------------------------------------------------------------
/fix_duplicate_aliases.php:
--------------------------------------------------------------------------------
1 | $errors, 'html_output' => $html_output));
33 | ?>
--------------------------------------------------------------------------------
/fix_duplicate_terms.php:
--------------------------------------------------------------------------------
1 | 1 ) temp ON term_data.name= temp.name;";
19 |
20 | $result_dup_terms = runFetchFromDatabase($database_settings_array, $query_get_duplicate_terms, $errors);
21 | $duplicate_terms_count = count($result_dup_terms);
22 |
23 |
24 | if($duplicate_terms_count > 0) {
25 | foreach ($result_dup_terms as $row_key => $row_val) {
26 | $update_query = "UPDATE ".$database_settings_array['data'].".term_data ".
27 | "SET term_data.name = '".$row_val['name']."_".$row_val['tid']."' ".
28 | "WHERE tid=".$row_val['tid'].";";
29 |
30 | $html_output = runAlterDatabase($database_settings_array, $update_query, $errors);
31 | }
32 | }
33 |
34 | print json_encode(array('result' => $errors, 'html_output' => $html_output));
35 | ?>
--------------------------------------------------------------------------------
/fix_terms_charlength.php:
--------------------------------------------------------------------------------
1 | $errors, 'html_output' => $html_output));
23 | ?>
--------------------------------------------------------------------------------
/functions_database.php:
--------------------------------------------------------------------------------
1 | $row_val) {
19 | $node_types_params_list=$node_types_params_list."'".$row_val."'";
20 |
21 | if ($counter < $node_types_count-1) {
22 | $node_types_params_list = $node_types_params_list.", ";
23 | $counter++;
24 | }
25 | }
26 | }
27 |
28 | // Selected node types are converted to 'post'
29 | $query = "UPDATE ".$wp_settings_array['data'].".wp_posts ".
30 | "SET post_type = 'post' ".
31 | "WHERE post_type IN (".$node_types_params_list.");";
32 | // remaining are set to 'page'
33 | $query = $query." UPDATE ".$wp_settings_array['data'].".wp_posts ".
34 | "SET post_type = 'page' ".
35 | "WHERE post_type NOT IN ('post');";
36 | return $query;
37 | }
38 |
39 | /*
40 | * Gets nodes from Drupal and inserts them into WordPress
41 | */
42 | function buildQueryCreateWPPosts($wp_settings_array, $d_settings_array) {
43 |
44 | // Get all available Drupal node types
45 | $result = runFetchFromDatabase($d_settings_array, QUERY_DRUPAL_GET_NODE_TYPES, $errors);
46 | $node_types_count = count($result);
47 | $node_types_params_list = buildNodeTypesParamsList($result);
48 |
49 | // Insert associated nodes as WordPress posts
50 | $query = "INSERT INTO ".$wp_settings_array['data'].".wp_posts ".
51 | "(id, post_author, post_date, post_content, post_title, post_excerpt, ".
52 | "post_name, post_modified, post_type, post_status, to_ping, pinged, post_content_filtered) ".
53 | "SELECT DISTINCT ".
54 | "n.nid 'id', ".
55 | "n.uid 'post_author', ".
56 | /* ACCL:
57 | * Original query had "FROM_UNIXTIME(n.created) 'post_date', ".
58 | * FROM_UNIXTIME cannot handle dates prior to 1970. If a post date is somehow set to a date
59 | * before 1970, this returns a NULL and will cause the query to fail. DATE_ADD solves this.
60 | */
61 | "DATE_ADD(FROM_UNIXTIME(0), interval n.created second) 'post_date', ".
62 | "r.body 'post_content', ".
63 | "n.title 'post_title', ".
64 | "r.teaser 'post_excerpt', ".
65 | //No url alias? Use node ID. Strip directory path
66 | "IF(a.dst IS NULL, n.nid, SUBSTRING_INDEX(a.dst, '/', -1)) 'post_name', ".
67 | "DATE_ADD(FROM_UNIXTIME(0), interval n.changed second) 'post_modified', ".
68 | "n.type 'post_type', ".
69 | "IF(n.status = 1, 'publish', 'private') 'post_status', ".
70 | "' ', ".
71 | "' ', ".
72 | "' ' ".
73 | "FROM ".$d_settings_array['data'].".node n ".
74 | "INNER JOIN ".$d_settings_array['data'].".node_revisions r ".
75 | "USING(vid) ".
76 | "LEFT OUTER JOIN ".$d_settings_array['data'].".url_alias a ".
77 | "ON a.src = CONCAT('node/', n.nid) ".
78 | "WHERE n.type IN (".$node_types_params_list.");";
79 |
80 | return $query;
81 | }
82 |
83 |
84 | /*
85 | * room34.com: Fix post type; http://www.mikesmullin.com/development/migrate-convert-import-tindrupal6-5-to-wordpress-27/#comment-17826
86 | * Add more Drupal content types below if applicable.
87 | */
88 | function buildQuerySetWPPostType($node_types, $wp_settings_array, $d_settings_array) {
89 | $query = "UPDATE ".$wp_settings_array['data'].".wp_posts ".
90 | "SET post_type = 'post' ".
91 | "WHERE post_type IN (".$node_types.");";
92 | return $query;
93 | }
94 |
95 | /*
96 | * room34.com: Fix images in post content; uncomment if you're moving files from "files" to "wp-content/uploads".
97 | */
98 | function buildQueryUpdateFilepath($filepath, $wp_settings_array) {
99 | $query = "UPDATE ".$wp_settings_array['data'].
100 | ".wp_posts SET post_content = REPLACE(post_content, '\"".$filepath."', '\"/wp-content/uploads/');";
101 | return $query;
102 | }
103 |
104 | /*
105 | *
106 | */
107 | function buildQuerySetPermalinkStructure($wp_settings_array, $permalink_structure ) {
108 | $query = "UPDATE ".$wp_settings_array['data'].".wp_options ".
109 | "SET option_value = '$permalink_structure' ".
110 | "WHERE option_name = 'permalink_structure';";
111 |
112 | return $query;
113 | }
114 |
115 | /*
116 | * Builds node types in comma separated list for use in queries
117 | */
118 | function buildNodeTypesParamsList($params_array) {
119 |
120 | // Build node types list
121 | $node_types_count = count($params_array);
122 | $types_list="";
123 | $counter = 0;
124 |
125 | if ($node_types_count) {
126 | foreach ($params_array as $row_key => $row_val) {
127 | $types_list=$types_list."'".$row_val['type']."'";
128 | if ($counter < $node_types_count-1) {
129 | $types_list = $types_list.", ";
130 | $counter++;
131 | }
132 | }
133 | }
134 | return $types_list;
135 | }
136 |
137 | /*
138 | * room34.com: Auto-assign posts to category.
139 | * You'll need to work out your own logic to determine strings/terms to match.
140 | * Repeat this block as needed for each category you're creating.
141 | */
142 | function buildQueryAssignPostsToCategory($wp_settings_array) {
143 | $query = "INSERT IGNORE INTO ".$wp_settings_array['data'].".wp_term_relationships (object_id, term_taxonomy_id) ".
144 | "SELECT DISTINCT p.ID AS object_id, ".
145 | "(SELECT tt.term_taxonomy_id ".
146 | "FROM ".$wp_settings_array['data'].".wp_term_taxonomy tt ".
147 | "INNER JOIN ".$wp_settings_array['data'].".wp_terms t USING (term_id) ".
148 | "WHERE t.slug = 'enter-category-slug-here' ".
149 | "AND tt.taxonomy = 'category') AS term_taxonomy_id ".
150 | "FROM ".$wp_settings_array['data'].".wp_posts p ".
151 | "WHERE p.post_content LIKE '%enter string to match here%' ".
152 | "OR p.ID IN ( ".
153 | "SELECT tr.object_id ".
154 | "FROM ".$wp_settings_array['data'].".wp_term_taxonomy tt ".
155 | "INNER JOIN ".$wp_settings_array['data'].".wp_terms t USING (term_id) ".
156 | "INNER JOIN ".$wp_settings_array['data'].".wp_term_relationships tr USING (term_taxonomy_id) ".
157 | "WHERE t.slug IN ('enter','terms','to','match','here') ".
158 | "AND tt.taxonomy = 'post_tag');";
159 | return $query;
160 | }
161 |
162 |
163 | /*
164 | * Turns all Drupal terms into Drupal post tags
165 | *
166 | * Under WordPress, tags would be more numerous than categories.
167 | * It's more efficient to blanket convert all terms into tags,
168 | * then offer the choice to convert selected tags into categories.
169 | *
170 | * room34.com's similar query didn't work for me as it didn't correctly match
171 | * Drupal's term ID for use with WordPress. WordPress associates a post's object_id
172 | * with a tag or category via term_taxonomy_id in table wp_term_relationships.
173 |
174 | * term_taxonomy_id is the primary key of table wp_term_taxonomy.
175 | * Therefore, when their query ran, new term_taxonomy_id primary keys are created in
176 | * wp_term_taxonomy as term_ids are inserted. Thus, the term_taxonomy_id no longer
177 | * corresponds to Drupal's tid
178 | *
179 | * I fixed this by inserting Drupal's tid into term_taxonomy_id and term_taxonomy_id
180 | *
181 | */
182 | function buildQueryConvertDrupalTermsToWPTags($wp_settings_array, $d_settings_array) {
183 | $query = "INSERT INTO ".$wp_settings_array['data'].".wp_term_taxonomy ".
184 | "(term_taxonomy_id, term_id, taxonomy, description, parent) ".
185 | "SELECT DISTINCT ".
186 | "d.tid, ".
187 | "d.tid 'term_id', ".
188 | "'post_tag', ".
189 | "d.description 'description', ".
190 | "h.parent 'parent' ".
191 | "FROM ".$d_settings_array['data'].".term_data d ".
192 | "INNER JOIN ".$d_settings_array['data'].".term_hierarchy h ".
193 | "USING(tid) ".
194 | "INNER JOIN ".$d_settings_array['data'].".term_node n ".
195 | "USING(tid) WHERE (1); ";
196 |
197 | return $query;
198 | }
199 |
200 | /*
201 | *
202 | */
203 | function buildQuerySetCategories($wp_settings_array, $term_id_array) {
204 | $term_id_count = count($term_id_array);
205 | $term_id_list="";
206 | $counter = 0;
207 | if ($term_id_count) {
208 | foreach ($term_id_array as $row_key => $row_val) {
209 | $term_id_list=$term_id_list."'".$row_val."'";
210 | if ($counter < $term_id_count-1) {
211 | $term_id_list = $term_id_list.", ";
212 | $counter++;
213 | }
214 | }
215 | }
216 |
217 | $query = "UPDATE ".$wp_settings_array['data'].".wp_term_taxonomy ".
218 | "SET taxonomy='category' WHERE term_id IN (".$term_id_list.");";
219 |
220 | // Update category counts.
221 | $query = $query." UPDATE ".$wp_settings_array['data'].".wp_term_taxonomy tt ".
222 | "SET count = ( ".
223 | "SELECT COUNT(tr.object_id) ".
224 | "FROM ".$wp_settings_array['data'].".wp_term_relationships tr ".
225 | "WHERE tr.term_taxonomy_id = tt.term_taxonomy_id);";
226 |
227 | return $query;
228 | }
229 |
230 | /*
231 | *
232 | */
233 | function buildQuerySetDefaultCategory($wp_settings_array, $term_id) {
234 | $query = "UPDATE ".$wp_settings_array['data'].".wp_options SET option_value='$term_id' WHERE option_name='default_category';";
235 |
236 | // Make sure the selection is set as a category
237 | // It might already have been done by buildQuerySetCategories but do it anyway
238 | $query = $query." UPDATE ".$wp_settings_array['data'].".wp_term_taxonomy SET taxonomy='category' WHERE term_id=$term_id;";
239 |
240 | return $query;
241 | }
242 |
243 |
244 | /*
245 | "QUERY_MIGRATE_POST_NAME", "UPDATE ".$wp_settings_array['data'].".wp_posts ".
246 | "SET post_name = ".
247 | "REVERSE(SUBSTRING(REVERSE(post_name),1,LOCATE('/',REVERSE(post_name))-1));"
248 | */
249 |
250 |
251 | // Queries that get data from the database
252 | function runFetchFromDatabase($database_settings_array, $query, &$errors) {
253 | $result = array();
254 | try {
255 | $dsn = "mysql:host=".$database_settings_array['host'].";dbname=".$database_settings_array['data'];
256 | $conn = new PDO($dsn, $database_settings_array['user'], $database_settings_array['pass']);
257 | $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
258 | $stmt = $conn->prepare($query);
259 | $stmt->execute();
260 | $result = $stmt->fetchAll();
261 |
262 | if ( count($result) ==0 ) {
263 | //$errors.= "No rows returned.";
264 | error_log($query, 0);
265 | error_log("No rows returned", 0);
266 | }
267 | } catch(PDOException $e) {
268 | $errors = $errors." ".
269 | "Query: ".$query." ".
270 | "Error: ". $e->getMessage()." ";
271 | }
272 | return $result;
273 | }
274 |
275 | // Queries that alter the database but don't need result
276 | function runAlterDatabase($database_settings_array, $query, &$errors) {
277 | $row_count = 0;
278 |
279 | try {
280 | $dsn = "mysql:host=".$database_settings_array['host'].";dbname=".$database_settings_array['data'];
281 | $conn = new PDO($dsn, $database_settings_array['user'], $database_settings_array['pass']);
282 | $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
283 | $stmt = $conn->prepare($query);
284 | $stmt->execute();
285 | $row_count = $stmt->rowCount();
286 | } catch(PDOException $e) {
287 | $errors = $errors." ".
288 | "Query: ".$query." ".
289 | "Error: ". $e->getMessage()." ";
290 |
291 | error_log("runAlterDatabase(): dsn: $dsn", 0);
292 | error_log("runAlterDatabase(): query: $query", 0);
293 | error_log("runAlterDatabase(): Error: $errors", 0);
294 | }
295 |
296 | return $row_count;
297 | }
298 |
299 |
300 | // Tests a database connection
301 | function testDatabaseConnection($database_settings_array, &$errors) {
302 | $success = false;
303 | try{
304 | $conn = new pdo( "mysql:host=".$database_settings_array['host'].";dbname=".$database_settings_array['data'],
305 | $database_settings_array['user'],
306 | $database_settings_array['pass'],
307 | array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
308 | if ($conn) {
309 | $success = true;
310 | }
311 | }
312 | catch(PDOException $e){
313 | $errors = $errors."Error: ". $e->getMessage();
314 | }
315 | return $success;
316 | }
317 | ?>
318 |
--------------------------------------------------------------------------------
/functions_display.php:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 | Drupal to WordPress migration tool
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14 |
15 |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16 |
17 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.