Migrating from ExpressionEngine to WordPress using an XML / WXR import file

Aug 23 2012

One of my first projects here at RD2 was to migrate a site from ExpressionEngine over to WordPress. Part of that process involved making a hard decision:

“Do we migrate the content manually, create a custom database migration script, or is there a better way?”

It turned out that there was a much better way to accomplish what we were after. It was a little custom, but more importantly – it was reusable. The solution I came up with was to create a template within ExpressionEngine, that would output a WordPress XML / WXR import file. That sounds twisted, right?

Because we believe in open source and that it would truly be a disservice to the community of users and developers to keep this sort of thing to ourselves, we’ve decided to release this code snippet for everyone to use. Now thinking about migrating from ExpressionEngine to WordPress might not be such a challenge. You can tweak this to serve your own specific needs; I’ve set it up in a way that should cover enough ground for many use-cases.

Step 1

Create a new template in ExpressionEngine, put it in the root of your site, name it something like “my-export” for the URL.

Step 2

Set the “Type” to “XML”, mark the template to “Allow PHP”, allow “PHP Parse on Output”, and restrict the role to “Admin” so not just anyone can grab your data.

Step 3

Place the following code in the template and save.

EE->db;
echo '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?' . '>';
?>




















 {exp:xml_encode}{site_name}{/exp:xml_encode}
 {site_url}
 Site
 2012-03-22T00:00:00 +0000
 en
 1.0
 {site_url}
 {site_url}
 http://expressionengine.com/

{exp:channel:entries channel="{segment_2}" orderby="date" sort="desc" status="open|featured|home_news" limit="9999" dynamic_start="yes" cache="no" refresh="0" disable="member_data|categories|category_fields|pagination"}
 
 {exp:xml_encode}{title}{/exp:xml_encode}
 {title_permalink}
 {entry_date format='%Y-%m-%d %h:%i:%s'}
 {exp:xml_encode}{author}{/exp:xml_encode}
 
 
 
 1000{entry_id}
 {entry_date format='%Y-%m-%d %h:%i:%s'}
 {gmt_entry_date format='%Y-%m-%d %h:%i:%s'}
 open
 open
 {exp:xml_encode}{url_title}{/exp:xml_encode}
 publish
 0
 0
 {segment_3}
 
 0
 

query( "SELECT
 exp_channel_fields.field_id,
 exp_channel_fields.field_name,
 exp_channel_fields.field_type
 FROM exp_channels
 LEFT JOIN exp_channel_fields
 ON exp_channel_fields.group_id = exp_channels.field_group
 WHERE exp_channels.channel_id = {channel_id}
 AND exp_channels.site_id = {entry_site_id}
 ORDER BY exp_channel_fields.field_order" );
 $content = '';
 $attachment = '';
 if ( 0 < $query->num_rows ) {
 foreach( $query->result_array() as $row ) {
 $field = $DB->query( "SELECT field_id_" . $row[ 'field_id' ] . " AS field_value FROM exp_channel_data WHERE entry_id = {entry_id} AND site_id = {entry_site_id} AND channel_id = {channel_id}" );
 if ( 0 == $field->num_rows )
 continue;
 $field = current( $field->result_array() );
 $field_value = $field[ 'field_value' ];
 $field_value = str_replace( '{' . 'filedir_3' . '}', 'http://' . $_SERVER[ 'HTTP_HOST' ] . '/images/backgrounds/', $field_value);
 if ( '{segment_4}' == $row[ 'field_name' ] )
 $content = $field_value;
 if ( '{segment_5}' == $row[ 'field_name' ] )
 $attachment = $field_value;
?>
 
 
 ]]>
 

 
 _thumbnail_id
 
 
 
{!--
EE->TMPL->modules)) {
 global $IN;
 $IN->QSTR = '{entry_id}';*/
?>
 {exp:comment:entries weblog="ipr_digest" sort="asc"}
 
 {comment_id}
 
 {email}
 {url}
 {ip_address}
 {comment_date format="%Y-%m-%d %h:%i:%s"}
 {gmt_comment_date format="%Y-%m-%d %h:%i:%s"}
 
 1
 
 0
 
 
 {/exp:comment:entries}

--}
 ]]>
 

 
 {exp:xml_encode}{title}{/exp:xml_encode}
 {title_permalink}-attachment
 {entry_date format='%Y-%m-%d %h:%i:%s'}
 {exp:xml_encode}{author}{/exp:xml_encode}
 
 
 
 
 2000{entry_id}
 {entry_date format='%Y-%m-%d %h:%i:%s'}
 {gmt_entry_date format='%Y-%m-%d %h:%i:%s'}
 closed
 closed
 {exp:xml_encode}{url_title}{/exp:xml_encode}-attachment
 inherit
 1000{entry_id}
 0
 attachment
 
 0
 
 

{/exp:channel:entries}



Step 4

Then simply fill in the portions of this example URL with the real parts. Parameters should be in all lowercase; I’ve capitalized them for readability.

mysite.com / my-export / CHANNEL / POST_TYPE / CONTENT_FIELD / FEATURED_IMG_FIELD

Step 5

Save the output into a file locally you get as something like “ee-import-to-wp.xml” — in a browser just use the “Save As..” option, the code above will automatically tell your browser it’s an XML file.

Step 6

Go to the WordPress Import screen located at Tools » Import area of your WordPress Dashboard (/wp-admin/admin.php?import=wordpress), select the file and click “Upload file and import”

Step 7

Follow the instructions on the screen from there, if prompted, choose to import attachments / images, this will save them into your WordPress site instead of linking to them on your ExpressionEngine site.

Enjoy

It’s time to sit back and enjoy, or you can rinse and repeat for additional post types / channels by following steps 4-7 again.

Tags

2 Comments to “Migrating from ExpressionEngine to WordPress using an XML / WXR import file”

  1. This is a good method and I approve, but the problem with ExpressionEngine sites is that frequently, they’re highly custom. Using various extensions of some kind to add tags, for example, or having customized author fields, etc. So the template will work only in the generic sense, it would likely have to be customized to the particular site’s requirements if the site is at all complex.

    For a migration I helped with, I wrote a PHP script to essentially read the relevant data from the EE database directly, and make the proper WP function calls to insert the data into a fresh WP database. This turned out to be easier than expected, and it’s a valid option if you know your way around PHP and can find the data you need in the EE database. For complex migrations, I’d consider that to be a better approach, simply because you can make your migration more complex to support whatever the new site that you are designing is going to be. For example, if you need to have custom taxonomies, then you can create the taxonomy in advance, then migrate the old data into the new database, and have code to preserve the taxonomy in the process.

    When doing any migration, it’s best to plan out and create the new site in advance, without any data, first. Migrating the data in should be the last step, basically. Writing a custom script to do the migration then gives you more control over the process.

    By Otto on August 23rd, 2012 at 11:41 am
  2. Totally agree, there are a few ways to go at it. The data we were migrating was pretty simple and we didn’t have to do any custom PHP / DB migration scripting, just set this up and it was reusable for the whole site’s content.

    By Scott Kingsley Clark on August 27th, 2012 at 5:38 pm

Leave a Reply