Gopher converter // Creates a set of text files and gophermap files based on the contents of a // WordPress database, allowing the blog to be served out via pygopherd or // bucktooth (untested). // This tool *will remove all files and directories* from the target directory. // It will create a series of subdirectories, each containing a gophermap pointing // to 50 posts, to keep listing sizes reasonable. The intent is for you to // have a single directory which is created and re-created by this script and // not manually modified. // All post files are physically located in a single directory so that the location // of posts does not change as more posts are added and they shift from page to page. // ********************* // CONFIGURATION SECTION // Change the variables below to the appropriate values for your server setup. // Set this to TRUE to enable console output showing conversion status. // When FALSE, output will only be written in the case of an error. This is // ideal if this script is to be run as a cronjob. $verbose = TRUE; // These should be the same MySQL credentials used by WordPress $dbserver = 'localhost'; $dbuser = 'USERNAME'; $dbpass = 'PASSWORD'; $dbname = 'DBNAME'; // Change this if you have changed WordPress's default MySQL table prefix $dbtableprefix = 'wp_'; $blogname = "My Phantabulous Phlog"; $httpurl = "http://www.myblog.com"; $email = "comments@myblog.com"; // The location where the gopher files should be created // Don't forget the trailing slash! $gopherpath = "/var/gopher/phlog/"; // The absolute resource path to the directory for your blog. // For example, if my root menu is at /var/gopher and I am creating blog files // in /var/gopher/phlog, I would enter /phlog/ here. // Don't forget the beginning and trailing slashes! $virtualgopherpath = "/phlog/"; $maxlinelength = 75; // This will be displayed at the header of every page. The default incorporates the blog // name and URL set above, so you can leave it alone if you like. $headermsg = $blogname . "\nMost recent posts first ~ Also available at " . $httpurl; // A message to display at the end of each post, such as an invitation to comment. The // default incorporates the email address and URL set above, so you can leave it alone // if you like. // If you are also using WPTelnet, you might want to mention the availability // of comments via telnet in your footer. $footermsg = "\n\nTo submit a comment on this post, email " . $email . " or visit us on the web [ " . $httpurl . " ]."; // Number of posts per page. $postsperpage = 50; // END CONFIGURATION SECTION // Change code beyond this point at your own peril // ************************* function delete_directory($dirname) { // Delete the contents of a directory recursively if (is_dir($dirname)) $dir_handle = opendir($dirname); if (!$dir_handle) return false; while($file = readdir($dir_handle)) { if ($file != "." && $file != "..") { if (!is_dir($dirname."/".$file)) { unlink($dirname."/".$file); } else { delete_directory($dirname.'/'.$file); } } } closedir($dir_handle); rmdir($dirname); return true; } function textize($instring) { // String replacement rules for post contents, titles, and comments. // This is not a comprehensive list, but it deals with common formatting // tags and all of the special (i.e. foreign language) characters in // my blog's database. If you have a lot of goofy HTML character codes you // may need to add them here. $replaces = array( "" => "_", "" => "_", "" => "*", "" => "*", "" => "*", "" => "*", "" => "_", "" => "_", "" => "_", "" => "_", "
    " => "", "
" => "", "" => "", "
  • " => "* ", "
  • " => "\n", "
    " => "\n", "
    " => "-=-=-=-=-=-=-=-=-=-=\n", "
    " => "\n-=-=-=-=-=-=-=-=-=-=", "“" => "\"", "”" => "\"", "“" => "\"", "”" => "\"", "‘" => "'", "’" => "'", "‘" => "'", "’" => "'", "…" => "...", "–" => "-", "–" => "-", "—" => "--", "—" => "--", "™" => "(tm)", "™" => "(tm)", "©" => "(C)", "®" => "(R)", "&" => "&", "&" => "&", "â" => "a", "ă" => "a", "ş" => "s", "…" => "...", "°" => " degrees ", " " => " ", "ñ" => "n" ); // The following HTML tags will not be stripped if they still appear after // processing the above string replacements $specialtags = ""; // Do string replacements on the text for better plain-text display foreach($replaces as $chgfrom => $chgto) { $instring = str_replace($chgfrom, $chgto, $instring); } // Strip all HTML tags that still exist and won't be specially dealt with // from the text. $instring = strip_tags($instring, $specialtags); // Deal with tags while(($sttagoffset = strpos($instring, "", $sttagoffset+1); // Remove the entire opening tag $instring = substr($instring, 0, $sttagoffset) . substr($instring, $edtagoffset+1); // Now find the closing tag and replace it with our replacement text $sttagoffset = strpos($instring, ""); $instring = substr($instring, 0, $sttagoffset) . $tagreplace . substr($instring, $sttagoffset+4); } // Deal with tags while(($sttagoffset = strpos($instring, "", $sttagoffset+1); $instring = substr($instring, 0, $sttagoffset) . $tagreplace . substr($instring, $edtagoffset+1); } return($instring); } function start_gophermap($gopherpath, $virtualgopherpath, $headermsg, $currentpage, $totalposts, $postsperdir) { // Create a new directory, create a new gophermap file, and write header if(!mkdir($gopherpath . "page" . $currentpage)) die("Could not create directory."); if(!($gophermap = fopen($gopherpath . "/page" . $currentpage . "/gophermap", "w"))) die("Could not open gophermap file for write access."); fwrite($gophermap, $headermsg . "\n"); fwrite($gophermap, "Page " . $currentpage . " of " . ceil($totalposts/$postsperdir) . "\n\n"); if($currentpage != 1) fwrite($gophermap, "1Previous Page\t" . $virtualgopherpath . "page" . ($currentpage - 1) . "\n"); if($currentpage < ceil($totalposts/$postsperdir)) fwrite($gophermap, "1Next Page\t" . $virtualgopherpath . "page" . ($currentpage + 1) . "\n"); fwrite($gophermap, "1View All Posts\t" . $virtualgopherpath . "posts\n"); fwrite($gophermap, "\n"); return($gophermap); } // Execution begins here $stime=microtime(3); // Establish DB connection if(!(mysql_connect($dbserver, $dbuser, $dbpass))) die("Could not connect to DB server."); if(!(mysql_select_db($dbname))) die("Could not select WordPress DB."); // Clear out the directory if(!delete_directory($gopherpath)) die("Could not empty directory " . $gopherpath); if(!mkdir($gopherpath)) die("Could not recreate directory " . $gopherpath); if(!mkdir($gopherpath . "posts")) die("Could not create directory " . $gopherpath . "posts"); // Start the "all posts" gopher map if(!($allgophermap = fopen($gopherpath . "/posts/gophermap", "w"))) die("Could not open 'all posts' gophermap file for write access."); fwrite($allgophermap, $headermsg . "\n"); fwrite($allgophermap, "All Posts\n\n"); fwrite($allgophermap, "1Return to Page 1\t" . $virtualgopherpath . "page1\n\n"); $currentpage = 1; $currentpost = 1; // Get posts $query = "SELECT * FROM " . $dbtableprefix . "posts WHERE post_type = 'post' AND post_status = 'publish' ORDER BY post_date DESC"; $postsresult = mysql_query($query); $totalposts = mysql_num_rows($postsresult); $gophermap = start_gophermap($gopherpath, $virtualgopherpath, $headermsg, $currentpage, $totalposts, $postsperpage); while($row = mysql_fetch_array($postsresult)) { $post_author = $row['post_author']; $post_date = $row['post_date']; $post_content = $row['post_content']; $post_title = $row['post_title']; $comment_count = $row['comment_count']; $postID = $row['ID']; if($verbose) echo "Processing post " . $postID . "\n"; // Get the author's display name $query = "SELECT display_name FROM " . $dbtableprefix . "users WHERE ID = " . $post_author; $authorresult = mysql_query($query); $data = mysql_fetch_array($authorresult); $display_name = $data['display_name']; $post_content = textize($post_content); $post_title = textize($post_title); // Create header lines $postheader = strtoupper($post_title) . "\n(Posted " . $post_date . " by " . $display_name . ")\n\n"; if($comment_count > 1) $commentblockheader = "\n\n--------\n\nThere are " . $comment_count . " comments on this post:\n"; if($comment_count == 1) $commentblockheader = "\n\n--------\n\nThere is 1 comment on this post:\n"; if($comment_count == 0) $commentblockheader = "\n\n--------\n\nThere are no comments on this post.\n"; // Write post data out to a file $postfilename = $gopherpath . "/posts/post" . $postID . ".txt"; $postfile = fopen($postfilename, "w"); if(!$postfile) die("Could not open file " . $postfilename . " for write access."); fwrite($postfile, $postheader); fwrite($postfile, wordwrap($post_content, $maxlinelength)); fwrite($postfile, $commentblockheader); // Get comments for this post // The goofy OR in this query is because Akismet changes the type of the comment_approved field when installed, // I believe. $query = "SELECT * FROM " . $dbtableprefix . "comments WHERE comment_post_ID = " . $postID . " AND (comment_approved = '1' OR comment_approved = 1) ORDER BY comment_date ASC"; $commentsresult = mysql_query($query); $commentcounter = 1; while($commentsdata = mysql_fetch_array($commentsresult)) { $commentheader = "\nComment #" . $commentcounter . " by " . $commentsdata['comment_author'] . " ( " . $commentsdata['comment_author_email'] . " ) on " . $commentsdata['comment_date'] . "\n"; fwrite($postfile, wordwrap($commentheader, $maxlinelength)); fwrite($postfile, wordwrap(textize($commentsdata['comment_content']), $maxlinelength)); fwrite($postfile, "\n"); $commentcounter++; } // Done writing the file for this post; close file and add a link to the gophermap fwrite($postfile, wordwrap($footermsg, $maxlinelength)); fclose($postfile); $gophermapline = "0" . $post_title . "\t" . $virtualgopherpath . "posts/post" . $postID . ".txt\n"; fwrite($gophermap, $gophermapline); fwrite($allgophermap, $gophermapline); if($currentpost % 50 == 0 && $currentpost < $totalposts) { fclose($gophermap); $currentpage++; $gophermap = start_gophermap($gopherpath, $virtualgopherpath, $headermsg, $currentpage, $totalposts, $postsperpage); } $currentpost++; } // Done processing posts; close the gophermap file fclose($gophermap); fclose($allgophermap); if ($verbose) echo "Successfully processed all posts!\n"; $etime=microtime(3); if ($verbose) echo "Finished processing in " . ($etime-$stime) . " seconds.\n"; ?>