I had to relaunch a web site using Serendipity with a new Drupal installation. The old site had about 100 blog posts and lots of comments we did not want to lose. After some googling, i found this excellent blog post on creating drupal nodes programmatically and this useful resource on drupal node fields. i knocked up the following script that does the job. Just copy this to s9y-import.php and place it in your web root and call it in your web browser. Please back up your database in case something goes wrong (believe me, if you have no backup, it will go wrong!). You might want to read the code comments to know about the current limitations.

Read the full post to see the source code.

 _updated on 14.7.2009_


<?php

/ ** * Import s9y blog posts along with comments into drupal** 
 ** Simply put this into your drupal installation root and call it with a web browser** 
 ** Known limitations:** 
 ** _   - You need to be logged into drupal as the admin user (id = 1) and all entries will appear to come from that user_   - Assumes that both s9y and drupal use the same encoding.** 
 ** _   - Requires s9y and drupal to share one database. If this is not the case, just copy the serendipity_entries and serendipity_comments tables to the drupal database_** 
 ** _*   - If your s9y tables do not start with serendipity_ or your drupal tables not with drupal_ you will need to adjust the SQL statements_   - Does not take care of contained images. You either have to keep the <s9y>/uploads/ directory online or you copy the contents of that folder to the drupal path and adjust the src attributes using SQL.** 
 ** _   - Will make your whole body the teaser, meaning the complete entry is shown in the overview._** 
 ** _          The reason is that s9y handles teaser and full entry differently from drupal._** 
 ** _          You can edit over-long posts and just save them to have an automatic short teaser._** 
 ** * Written by <david.buchmann@liip.ch> on 18.6.2009** 
 ** * Inspired by this blog post:** 
 **     http://vhata.net/blog/2007/03/27/converting-from-serendipity-to-drupal** 
 ** _/_** 
 ** _/_**  configuration     _ **/** _
 _ **/**  map s9y user id to drupal id._ the format is s9yID => drupalID


 * entries not found in this map are assigned to uid 1, which is the admin user.

/

$usermap = array(1=>1);


 _/*** end configuration ***/_
 _require_once './includes/bootstrap.inc';_
 _drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);_
 _error_reporting(E_ALL);_
 _$mappings = ''; //hold url mapping info_
 _$q = mysql_query("select id, title, timestamp, last_modified, CONCAT(body,extended) as body, authorid, comments from serendipity_entries order by timestamp asc");_
 _while($f = mysql_fetch_assoc($q)) {_
 _    $userid = isset($usermap[$f['authorid']]) ? $usermap[$f['authorid']] : 1;_
 _    $newent = array('created' => $f['timestamp'], 'changed' => $f['last_modified'], 'title' => $f["title"], 'body' => $f["body"],_
 _        'teaser' => $f["body"], 'format' => 2, 'uid' => $userid, 'type' => 'blog', 'status' => 1, "comment" => 2,_
 _        'promote' => 1, 'sticky' => 0);_
 _    $newento = (object)$newent;_
 _    node_save($newento);_
 _    //we can not directly get the new node id :-(_
 _    //we hope there is only one at that exact moment_
 _    $qid = mysql_query("select nid from drupal_node where created = ".$f['timestamp']);_
 _    $newid = mysql_fetch_assoc($qid);_
 _    if(! $newid) {_
 _        echo 'Could not retrieve newly inserted node<br />';_
 _        continue;_
 _    }_
 _    $newid = $newid['nid'];_
 _    //url for rewrite_
 _    $qurl = mysql_query("select dst from drupal_url_alias where src='node/$newid'"); //if you use more than 1 language, you will need to consider language column_
 _    $newurl = mysql_fetch_assoc($qurl);_
 _    $newurl = $newurl['dst'];_
 _    $qurl = mysql_query("select permalink from serendipity_permalinks where type='entry' and entry_id=".$f['id']);_
 _    $oldurl = mysql_fetch_assoc($qurl);_
 _    $oldurl = $oldurl['permalink'];_
 _    $mappings .= "$oldurlt$newurln";_
 _    if ($f['comments'] > 0) {_
 _        $c = mysql_query('select id, parent_id, timestamp, title, author, email, url, ip, body FROM serendipity_comments WHERE entry_id = '.$f['id'].' ORDER BY parent_id');_
 _        $map = array(); //index: old parent id, value: new parent id_
 _        while($com = mysql_fetch_assoc($c)) {_
 _            $parent = 0;_
 _            if ($com['parent_id'] != 0) $parent = $map[$com['parent_id']];_
 _            $newcom = array('pid' => $parent,_
 _                            'nid' => $newid,_
 _                            'uid' => 0,_
 _                            'subject' => $com['title'],_
 _                            'comment' => $com['body'],_
 _                            'hostname' => $com['ip'],_
 _                            'timestamp' => $com['timestamp'],_
 _                            'status' => COMMENT_PUBLISHED,_
 _                            'format' => 1,_
 _                            //thread field is determined by comment_save function_
 _                            'name' => $com['author'],_
 _                            'mail' => $com['email'],_
 _                            'homepage' => $com['url']);_
 _            //funny: comment_save returns the id, whereas node_save does not_
 _            $newcid = comment_save($newcom);_
 _            $map[$com['id']] = $newcid;_
 _        }_
 _    }_
 _}_
 _?>_
 _Copy the following into your apache config or .htaccess file and adjust paths_
 _<pre>_
 _RewriteEngine on_
 _RewriteMap urlmap "txt:/path/to/file/urlmap.txt"_
 _RewriteRule ^/blog/(._) /site/${urlmap:$1} [L,NC,QSA,R]


</pre>


And copy this into urlmap.txt


<pre>


<?php echo $mappings ?>


</pre>

This script only does what I needed at the moment, it is not the perfect importer. It took about 10 seconds to import the 100 blog posts. Then it took me maybe 2 hours going through the blog and fixing links between blog posts and editing the long entries to enter the teaser border.

If you improved the script, please share your improvements here.