Migrating existing Wordpress content to a new Kirby site

Hi there,
I am working on the redesign of a pretty large Wordpress-based website (lots of blog posts dating back to 2010, with many comments etc) and I would love to use Kirby as the new CMS. However, I am not sure I understand what the best way to migrate the existing content would be. I looked through the forum’s archives but couldn’t find something of much use. The website will continue being heavily used, so new content will be added regularly.

Any help would be much appreciated!

Thank you.

There are two plugins that might help get you going:

1 Like

I’m trying to do exactly what Kalli was attempting back in June. I managed to get ‘wordpress-xml-to-kirby’ to export something, but it’s very inconsistent and not reliable. I’d like to try WxrKirby but the documentation is a bit vague. Where am I supposed to put my target xml and export folder URLs and how do I run the script? @WebMechanic maybe you can help?

This is my first experience with Wordpress, and it’s a pretty horrible one!

Thanks for your interest in WxrKirby.
Just for the record: WxrKirby is NOT a plugin but a standalone command line utility.

As you can see in the repo there was no significant update after the initial upload almost 8 months ago… It still only handles page and blog conversions to Markdown using the site structure of the WP setup.
I got stuck at moving the wp-upload images to Kirby’s ./assets and then Corona kicked in.
Since I also only had a moderatly sized customer site project to test this with moving images manually was faster :slight_smile: and I had to get the actual project move forward.

As shown in the small code example of the readme the (absolute) path to the XML output is passed to the constructor, so you can have it wherever you want. For simplicity put both in the same folder, i.e the xml and your “migrate.php” using the sample code provided; adapt as you see fit.
IIRC you can use any of WP XML dumps, ie full site or just the “pages and blog” flavour (don’t recall the name). Just keep in mind that any plugin generated content (incl. galleries) are essentially ignored.

Then have a look at the config $options of the Converter class esp. where to put the output files and folders (see sample).
This folder would evtl. become your Kirby site ./content folder “as is”. You’d then however need to move the contents of the wp-upload folder.

This is where I didn’t proceed, so URLs and links to images etc to wp-upload are not converted to a Kirby asset or content URL. The original paths and links (if present) are stored as Kirby fields inside each page file. A search-replace aver the markdown files command might do the trick to adjust them to suit your Kirby site setup.
If your WP site uses custom templates the markdown file would be called that instead of default.txt

The idea was to run the converter script on the command line simply as php migrate.php. You might run into issue with resolving class files.
Although I won’t support composer.json you can use it’s bootloader to declare the class paths, i.e.

<?php
namespace WebMechanic\Converter;
/* use Composer Loader and add our local class paths */
$loader = require_once( '/path/to/kirby-folder/vendor/autoload.php');
$loader->setUseIncludePath(true);
/* path of Converter.php */
$loader->add('Converter', realpath(__DIR__));
$loader->add('Converter\Kirby', realpath(__DIR__) . '/Kirby');
$loader->add('Converter\Wordpress', realpath(__DIR__) . '/Wordpress');

class Migrator extends Converter
{
	public function __construct($xmlfile) {
		static::$options['paths']['create'] = true;
		parent::__construct($xmlfile);
	}
}
$M = new Migrator('export.wordpress.pages.2020-02-20.xml');
$M->convert();
$M->getSite()->writeOutput();
foreach ($M->getPages() as $page) {
  $page->writeOutput();
}

Hope this helps.

If you have issues please head over to the repo. I’ll try to answer there.
Thanks.

[EDIT] added better constructor as as result of this discussion.

1 Like

Thanks for this! I’ve tried using your code to do a test, but I keep getting Class not found:

Fatal error: Class 'WebMechanic\Converter\Converter' not found in /Applications/MAMP/htdocs/BFS-convert/Migrator.php on line 10

I may be a bit out of my depth here, so will probably look for an alternate way of migrating from Wordpress. I’ve had some success converting the Wxr to Markdown, but it will still require so much manual work I’m not even sure it’s worth it.

The code will only run if you use an autoloader like the one provided with Composer. There’s one in Kirby’s /vendor folder.
Change $loader = require_once(__DIR__ . '/autoload.php'); to load that file.

Make sure any of the three $loader->add() statements point to the correct folder. Note that realpath(__DIR__) will return the parent folder of your script’s location! If in doubt remove realpath() and insert the absolute paths to all three folders.

You might then run into an uncaught exception if you ommit some $options, so please (at least) add this _constructor() method to your class:

class Migrator extends Converter
{
	public function __construct($xmlfile) {
		static::$options['paths']['create'] = true;
		parent::__construct($xmlfile);
	}
}

This will autocreate an output folder structure as a sibling to the folder your script is in, in your case this would be: /Applications/MAMP/htdocs/migration
Remove all the extra code below the class and test this code.

Then add $M = new Migrator(""); (no xml file given!) below the class and test again
It’ll do nothing but create the ‘migration’ output folder.
You could print_r($M) to examine the object. FYI: the $WXR property will eventually contain the reader class with all the XML data structures.

Now passs the xml file path and try again.
A print_r($M) will now turn into a massive text dump as it should include the contents of the XML file.

At this point you should be ready to go.

Add $M->convert() and the rest as shown in the example.

Good Luck!

I make this from wordpress-xml-to-kirby

It’s not ready to use, you need to put the class in theme folder and modify your current theme.
In the loop :

 $upOne = realpath(__DIR__ . '/../..');
 require $upOne.'/vendor/autoload.php';
 use League\HTMLToMarkdown\HtmlConverter; 
 $converter = new HtmlConverter(array('strip_tags' => true));
 $exportdir = 'export/';
 $markdown = $converter->convert(($content));

 // Strip WordPress caption shortcodes, optional

 $markdown = preg_replace("/\[caption(.*?)\]/", "", $markdown);
 $markdown = preg_replace("/\[\/caption\]/", "", $markdown);

 // Prepare various bits of content for the export

 $tmptitle = str_replace('%c2%b7', '-', $post->post_name); // · <- change this char to - in the slug
 $noslashes = preg_replace('/[^A-Za-z0-9\-]/', '', $tmptitle);
 $tmpyear = get_the_date('Y'); //date('Y', strtotime(get_the_date()));
 $tmpdate = get_the_date('Ymd');//date('Y/Ymd', strtotime(get_the_date()));
 $file = $exportdir . $tmpdate . '_' . $noslashes . '/article.fr.txt'; // <--- french
 $folder = $exportdir . $tmpdate . '_' . $noslashes;

 $cats = array();
 foreach (get_the_category() as $c) {
   $cat = get_category($c);
   array_push($cats, $cat->name);
 }

 $tags = array();
 if(get_the_tags()) :
   foreach (get_the_tags() as $t) {
     array_push($tags, $t->name);
   }
 endif;

 // Create the directory for the export

 if (!file_exists($folder)) {
  mkdir($folder, 0777, true);
 }


 $dom = new DOMDocument();
 $dom->loadHTML(get_the_content());
 $images = $dom->getElementsByTagName("img");


 foreach($images as $img){
   $src = $img->getAttribute('src');
   file_put_contents($folder.'/'.basename($src),get_content_curl($src));
 }

 $pj = $dom->getElementsByTagName("a");

 foreach($pj as $p){
   $href = $p->getAttribute('href');
   $query = 'http://www.example.com/wp-content/uploads';
 
   if(substr($url, 0, strlen($query)) === $query){
     file_put_contents($folder.'/'.basename($href),get_content_curl($href));
   }
 }

 $cover =array();
 $attachments= get_attached_media( 'image', $post->ID );
 foreach($attachments as $att_id => $attachment) {
   $full_img_url = wp_get_attachment_url($attachment->ID);
   array_push($cover,basename($full_img_url));
   file_put_contents($folder.'/'.basename($full_img_url),get_content_curl($full_img_url));
 }


 // Compile the content for the export

 $strtowrite = "Title: " . html_entity_decode(get_the_title())
   . PHP_EOL . "----" . PHP_EOL
   . "Date: " .  get_the_date('Y-m-d h:i')
   . PHP_EOL . "----" . PHP_EOL
   . "Category: " . implode(', ', $cats)
   . PHP_EOL . "----" . PHP_EOL
   . "Summary: "
   . PHP_EOL . "----" . PHP_EOL
   . "Tags: " . implode(', ', $tags)
   . PHP_EOL . "----" . PHP_EOL
   . "Coverimage: " . $cover[0]
   . PHP_EOL . "----" . PHP_EOL
   . "Text: " . $markdown;

 // Save the article.txt file

 file_put_contents($file, $strtowrite);

Put this in functions.php

function get_content_curl($curl){
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $curl);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
  $output = curl_exec($ch);
  curl_close($ch);
  return $output;
}

Is this (WxrKirby) still the best migration path?

Is this (WxrKirby) still the best migration path?

unsure about “the best”, but it works.
It certainly helps to know your way around PHP, as it can be extended almost at will.

Since this is an old topic and I consult both this forum and Discord only ever other leap year, I recommend posting questions and issues regarding WxrKirby’s usage over on Github.
I’m happy to help and answer questions over there.
Thank you.