Index: blogger-importer-blogitem.php
===================================================================
--- blogger-importer-blogitem.php	(revision 0)
+++ blogger-importer-blogitem.php	(working copy)
@@ -0,0 +1,80 @@
+<?php
+
+/**
+* Based on WP_SimplePieAtomPub_Item
+* Expect this to become part of core wordpress at some point.
+* See http://core.trac.wordpress.org/ticket/7652
+* 
+* Todo GeoTag parsing
+* http://codex.wordpress.org/Geodata
+* 
+ */
+
+define('SIMPLEPIE_NAMESPACE_ATOMPUB', 'http://www.w3.org/2007/app'); 
+	 
+	/** 
+	 * SimplePie Helper for AtomPub 
+	 * 
+	 * @package WordPress 
+	 * @subpackage Publishing 
+	 * @since 3.1 
+	 */ 
+if ( !class_exists( 'WP_SimplePie_Blog_Item' ) ) {    
+	class WP_SimplePie_Blog_Item extends SimplePie_Item { 
+	        /** 
+	         * Constructor 
+	         */ 
+	        function WP_SimplePieAtomPub_Item($feed, $data) { 
+	                parent::SimplePie_Item($feed, $data); 
+	        } 
+	 
+	        /** 
+	         * Get the status of the entry 
+	         * 
+	         * @return bool True if the item is a draft, false otherwise 
+	         */ 
+	        function get_draft_status() { 
+	                $draft = false; 
+	                if (($control = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOMPUB, 'control')) && !empty($control[0]['child'][SIMPLEPIE_NAMESPACE_ATOMPUB]['draft'][0]['data'])) { 
+	                        $draft = ('yes' == $control[0]['child'][SIMPLEPIE_NAMESPACE_ATOMPUB]['draft'][0]['data']); 
+	                } 
+	                return $draft; 
+	        } 
+ 
+            //Tried using date functions from http://core.trac.wordpress.org/attachment/ticket/7652/7652-separate.diff
+            //but ended up with 1970s dates so returned to Otto's version which is much simplified
+	        function get_updated() { 
+                $temparray = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated');
+                if ( isset( $temparray[0]['data'] ) ) return $this->convert_date($temparray[0]['data']);
+                else return NULL;
+            }
+	 
+            function get_published() { 
+                $temparray = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published');
+                if ( isset( $temparray[0]['data'] ) ) return $this->convert_date($temparray[0]['data']);
+                else return NULL;
+            }
+            
+            function convert_date( $date ) {
+            	    preg_match('#([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.[0-9]+)?(Z|[\+|\-][0-9]{2,4}){0,1}#', $date, $date_bits);
+            	    $offset = iso8601_timezone_to_offset( $date_bits[7] );
+            		$timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]);
+            		$timestamp -= $offset; // Convert from Blogger local time to GMT
+            		$timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time
+            		return gmdate('Y-m-d H:i:s', $timestamp);
+            	}    
+     
+            //Don't Sanitize the ID, the default get_id was cleaning our IDs and that meant that nested comments did not work      
+        	function get_id()
+        	{
+        			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'))
+        			{
+        				return $return[0]['data'];
+        			}
+             }
+       
+    }
+
+}
+
+?>
\ No newline at end of file
Index: blogger-importer-sanitize.php
===================================================================
--- blogger-importer-sanitize.php	(revision 0)
+++ blogger-importer-sanitize.php	(working copy)
@@ -0,0 +1,144 @@
+<?php
+/**
+ * New class to sanitize trusted content from blogger import
+ * Based on the SimplePie_Sanitize class by Ryan Parman and Geoffrey Sneddon
+ *
+ */
+
+class Blogger_Importer_Sanitize extends Simplepie_Sanitize
+{
+    // Private vars
+    var $base;
+
+    // Options
+    var $image_handler = '';
+    var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'font', 'form',
+        'frame', 'frameset', 'html', 'input', 'marquee', 'meta', 'script', 'style');
+    //Allow iframe (new style) and embed, param and object(old style) so that we get youtube videos transferred
+    //Allow object and noscript for Amazon widgets
+    var $encode_instead_of_strip = false;
+    var $strip_attributes = array('bgsound','class', 'expr', 'id', 'imageanchor', 'onclick', 'onerror',
+        'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc');
+    //Allow styles so we don't have to redo in Wordpress
+    //Brett Morgan from Google has confirmed that imageanchor is a made up attribute that is just used in the blogger editor so we can remove that
+    var $output_encoding = 'UTF-8';
+    var $enable_cache = true;
+    var $cache_location = './cache';
+    var $cache_name_function = 'md5';
+    var $cache_class = 'SimplePie_Cache';
+    var $file_class = 'SimplePie_File';
+    var $timeout = 10;
+    var $useragent = '';
+    var $force_fsockopen = false;
+
+    var $replace_url_attributes = array('a' => 'href', 'area' => 'href',
+        'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc',
+        'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite');
+
+	function _normalize_tag( $matches ) {
+		return '<' . strtolower( $matches[1] );
+	}
+
+    function sanitize($data, $type, $base = '')
+    {
+        //Simplified function
+        $data = trim($data);
+
+        // Remappings
+		$data = str_replace('<br>', '<br />', $data);
+		$data = str_replace('<hr>', '<hr />', $data);
+        //<span style="font-weight:bold;">Workshopshed:</span> > <b>Workshopshed:</b>
+        $data =  preg_replace('|(<span style="font-weight:bold;">)(?<!<span style="font-weight:bold;">).*(.*)(</span>)|', '<strong>$2</strong>', $data);
+        
+        //N.B. Don't strip comments as blogger uses <!--more--> which is the same as Wordpress
+
+        //Now clean up
+        foreach ($this->strip_htmltags as $tag) {
+            $pcre = "/<($tag)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$tag" .
+                SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>|(\/)?>)/siU';
+            while (preg_match($pcre, $data)) {
+                $data = preg_replace_callback($pcre, array(&$this, 'do_strip_htmltags'), $data);
+            }
+        }
+
+        foreach ($this->strip_attributes as $attrib) {
+            $data = preg_replace('/(<[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*)' .
+                SIMPLEPIE_PCRE_HTML_ATTRIBUTE . trim($attrib) . '(?:\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?' .
+                SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>/', '\1\2\3>', $data);
+        }
+
+        // Replace relative URLs
+        $this->base = $base;
+        foreach ($this->replace_url_attributes as $element => $attributes) {
+            $data = $this->replace_urls($data, $element, $attributes);
+        }
+
+        // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags.
+        if (isset($this->image_handler) && ((string )$this->image_handler) !== '' && $this->
+            enable_cache) {
+            $images = SimplePie_Misc::get_element('img', $data);
+            foreach ($images as $img) {
+                if (isset($img['attribs']['src']['data'])) {
+                    $image_url = call_user_func($this->cache_name_function, $img['attribs']['src']['data']);
+                    $cache = call_user_func(array($this->cache_class, 'create'), $this->
+                        cache_location, $image_url, 'spi');
+                    if ($cache->load()) {
+                        $img['attribs']['src']['data'] = $this->image_handler . $image_url;
+                        $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data);
+                    } else {
+                        $file = &new $this->file_class($img['attribs']['src']['data'], $this->timeout, 5,
+                            array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->
+                            force_fsockopen);
+                        $headers = $file->headers;
+                        if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->
+                            status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) {
+                            if ($cache->save(array('headers' => $file->headers, 'body' => $file->body))) {
+                                $img['attribs']['src']['data'] = $this->image_handler . $image_url;
+                                $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data);
+                            } else {
+                                trigger_error("$this->cache_location is not writeable", E_USER_WARNING);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // Having (possibly) taken stuff out, there may now be whitespace at the beginning/end of the data
+        $data = trim($data);
+        
+        // Normalise tags
+        $data = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $data);
+
+        return $data;
+    }
+
+    function replace_urls($data, $tag, $attributes)
+    {
+        if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) {
+            $elements = SimplePie_Misc::get_element($tag, $data);
+            foreach ($elements as $element) {
+                if (is_array($attributes)) {
+                    foreach ($attributes as $attribute) {
+                        if (isset($element['attribs'][$attribute]['data'])) {
+                            $element['attribs'][$attribute]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attribute]['data'],
+                                $this->base);
+                            $new_element = SimplePie_Misc::element_implode($element);
+                            $data = str_replace($element['full'], $new_element, $data);
+                            $element['full'] = $new_element;
+                        }
+                    }
+                } elseif (isset($element['attribs'][$attributes]['data'])) {
+                    $element['attribs'][$attributes]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attributes]['data'],
+                        $this->base);
+                    $data = str_replace($element['full'], SimplePie_Misc::element_implode($element),
+                        $data);
+                }
+            }
+        }
+        return $data;
+    }
+
+
+}
+?>
Index: blogger-importer.css
===================================================================
--- blogger-importer.css	(revision 0)
+++ blogger-importer.css	(working copy)
@@ -0,0 +1,29 @@
+
+td { text-align: center; line-height: 2em;}
+thead td { font-weight: bold; }
+.bar {
+	width: 200px;
+	text-align: left;
+	line-height: 2em;
+	padding: 0px;
+}
+.ind {
+	position: absolute;
+	background-color: #83B4D8;
+	width: 1px;
+	z-index: 9;
+}
+.stat {
+	z-index: 10;
+	position: relative;
+	text-align: center;
+}
+td.submit {
+	margin:0;
+	padding:0;
+}
+
+td {
+	padding-left:10px;
+	padding-right:10px;
+}
Index: blogger-importer.php
===================================================================
--- blogger-importer.php	(revision 545841)
+++ blogger-importer.php	(working copy)
@@ -2,12 +2,11 @@
 /*
 Plugin Name: Blogger Importer
 Plugin URI: http://wordpress.org/extend/plugins/blogger-importer/
-Description: Import posts, comments, tags, and attachments from a Blogger blog.
+Description: Imports posts, comments and tags from a Blogger blog then migrates authors to Wordpress users.
 Author: wordpressdotorg
 Author URI: http://wordpress.org/
-Version: 0.4
-Stable tag: 0.4
-License: GPL version 2 or later - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+Version: 0.6
+License: GPL v2 - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 */
 
 if ( !defined('WP_LOAD_IMPORTERS') )
@@ -16,6 +15,14 @@
 // Load Importer API
 require_once ABSPATH . 'wp-admin/includes/import.php';
 
+// Load Simple Pie
+require_once ABSPATH . WPINC . '/class-feed.php';
+require_once 'blogger-importer-sanitize.php';
+require_once 'blogger-importer-blogitem.php';
+
+// Load OAuth library
+require_once 'oauth.php';
+
 if ( !class_exists( 'WP_Importer' ) ) {
 	$class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php';
 	if ( file_exists( $class_wp_importer ) )
@@ -30,7 +37,7 @@
  * @var int
  * @since unknown
  */
-define( 'MAX_RESULTS',        50 );
+define( 'MAX_RESULTS', 25 );
 
 /**
  * How many seconds to let the script run
@@ -61,10 +68,27 @@
 if ( class_exists( 'WP_Importer' ) ) {
 class Blogger_Import extends WP_Importer {
 
+	function Blogger_Import() {
+		global $importer_started;
+		$importer_started = time();
+		if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) {
+			add_action('admin_print_scripts', array(&$this, 'queue_scripts'));
+            add_action('admin_print_styles', array(&$this, 'queue_style'));
+		}
+	}
+    
+    function queue_scripts($hook) {
+        wp_enqueue_script('jquery');
+    }
+    
+    function queue_style() {
+        wp_enqueue_style('BloggerImporter',plugins_url('/blogger-importer.css', __FILE__));
+    }
+
 	// Shows the welcome screen and the magic auth link.
 	function greet() {
 		$next_url = get_option('siteurl') . '/wp-admin/index.php?import=blogger&amp;noheader=true';
-		$auth_url = "https://www.google.com/accounts/AuthSubRequest";
+		$auth_url = $this->get_oauth_link();
 		$title = __('Import Blogger', 'blogger-importer');
 		$welcome = __('Howdy! This importer allows you to import posts and comments from your Blogger account into your WordPress site.', 'blogger-importer');
 		$prereqs = __('To use this importer, you must have a Google account and an upgraded (New, was Beta) blog hosted on blogspot.com or a custom domain (not FTP).', 'blogger-importer');
@@ -76,18 +100,46 @@
 		".screen_icon()."
 		<h2>$title</h2>
 		<p>$welcome</p><p>$prereqs</p><p>$stepone</p>
-			<form action='$auth_url' method='get'>
+			<form action='{$auth_url['url']}' method='get'>
 				<p class='submit' style='text-align:left;'>
 					<input type='submit' class='button' value='$auth' />
-					<input type='hidden' name='scope' value='http://www.blogger.com/feeds/' />
-					<input type='hidden' name='session' value='1' />
-					<input type='hidden' name='secure' value='0' />
-					<input type='hidden' name='next' value='$next_url' />
+					<input type='hidden' name='oauth_token' value='{$auth_url['oauth_token']}' />
+					<input type='hidden' name='oauth_callback' value='{$auth_url['oauth_callback']}' />
 				</p>
 			</form>
 		</div>\n";
 	}
+	
+	function get_oauth_link() {
+		// Establish an Blogger_OAuth consumer
+		$base_url = get_option('siteurl') . '/wp-admin';
+		$request_token_endpoint = 'https://www.google.com/accounts/OAuthGetRequestToken';
+		$authorize_endpoint = 'https://www.google.com/accounts/OAuthAuthorizeToken';
 
+		$test_consumer = new Blogger_OAuthConsumer('anonymous', 'anonymous', NULL); // anonymous is a google thing to allow non-registered apps to work
+
+		//prepare to get request token
+		$sig_method = new Blogger_OAuthSignatureMethod_HMAC_SHA1();
+		$parsed = parse_url($request_token_endpoint);
+		$params = array('callback' => $base_url, 'scope'=>'http://www.blogger.com/feeds/', 'xoauth_displayname'=>'WordPress');
+
+		$req_req = Blogger_OAuthRequest::from_consumer_and_token($test_consumer, NULL, "GET", $request_token_endpoint, $params);
+		$req_req->sign_request($sig_method, $test_consumer, NULL);
+
+		// go get the request tokens from Google
+		$req_token = wp_remote_retrieve_body(wp_remote_get($req_req->to_url(), array('sslverify'=>false) ) );
+
+		// parse the tokens
+		parse_str ($req_token,$tokens);
+
+		$oauth_token = $tokens['oauth_token'];
+		$oauth_token_secret = $tokens['oauth_token_secret'];
+
+		$callback_url = "$base_url/index.php?import=blogger&noheader=true&token=$oauth_token&token_secret=$oauth_token_secret";
+
+		return array('url'=>$authorize_endpoint, 'oauth_token'=>$oauth_token, 'oauth_callback'=>$callback_url );	
+	}
+
 	function uh_oh($title, $message, $info) {
 		echo "<div class='wrap'>";
 		screen_icon();
@@ -95,73 +147,58 @@
 	}
 
 	function auth() {
-		// We have a single-use token that must be upgraded to a session token.
-		$token = urldecode( preg_replace( '/[^%-_0-9a-zA-Z]/', '', $_GET['token'] ) );
-		$headers = array(
-			"GET /accounts/AuthSubSessionToken HTTP/1.0",
-			"Authorization: AuthSub token=\"$token\""
-		);
-		$request = join( "\r\n", $headers ) . "\r\n\r\n";
-		$sock = $this->_get_auth_sock( );
-		if ( ! $sock ) return false;
-		$response = $this->_txrx( $sock, $request );
-		preg_match( '/token=([%-_0-9a-z]+)/i', $response, $matches );
-		if ( empty( $matches[1] ) ) {
-			$this->uh_oh(
-				__( 'Authorization failed' , 'blogger-importer'),
-				__( 'Something went wrong. If the problem persists, send this info to support:' , 'blogger-importer'),
-				htmlspecialchars($response)
-			);
-			return false;
-		}
-		$this->token = urldecode( $matches[1] );
-
+		// we have a authorized request token now, so upgrade it to an access token		
+		$token = $_GET['token'];
+		$token_secret = $_GET['token_secret'];
+		
+		$oauth_access_token_endpoint  = 'https://www.google.com/accounts/OAuthGetAccessToken';
+		
+		// auth the token
+		$test_consumer = new Blogger_OAuthConsumer('anonymous', 'anonymous', NULL);
+		$auth_token = new Blogger_OAuthConsumer($token, $token_secret);
+		$access_token_req = new Blogger_OAuthRequest("GET", $oauth_access_token_endpoint);
+		$access_token_req = $access_token_req->from_consumer_and_token($test_consumer, $auth_token, "GET", $oauth_access_token_endpoint);
+		
+		$access_token_req->sign_request(new Blogger_OAuthSignatureMethod_HMAC_SHA1(),$test_consumer, $auth_token);
+		
+		$after_access_request = wp_remote_retrieve_body(wp_remote_get($access_token_req->to_url(), array('sslverify'=>false) ) );
+		
+		parse_str($after_access_request,$access_tokens);
+		
+		$this->token = $access_tokens['oauth_token'];
+		$this->token_secret = $access_tokens['oauth_token_secret'];
+	
 		wp_redirect( remove_query_arg( array( 'token', 'noheader' ) ) );
 	}
+	
+	// get a URL using the oauth token for authentication (returns false on failure)
+	function oauth_get($url, $params=NULL) {
+		$test_consumer = new Blogger_OAuthConsumer('anonymous', 'anonymous', NULL);
+		$goog = new Blogger_OAuthConsumer($this->token, $this->token_secret, NULL);
+		$request = new Blogger_OAuthRequest("GET", $url, $params);
 
-	function get_token_info() {
-		$headers = array(
-			"GET /accounts/AuthSubTokenInfo  HTTP/1.0",
-			"Authorization: AuthSub token=\"$this->token\""
-		);
-		$request = join( "\r\n", $headers ) . "\r\n\r\n";
-		$sock = $this->_get_auth_sock( );
-		if ( ! $sock ) return;
-		$response = $this->_txrx( $sock, $request );
-		return $this->parse_response($response);
-	}
+        //Ref: Not importing properly http://core.trac.wordpress.org/ticket/19096  
+		$blog_req = $request->from_consumer_and_token($test_consumer, $goog, 'GET', $url,$params);
 
-	function token_is_valid() {
-		$info = $this->get_token_info();
+		$blog_req->sign_request(new Blogger_OAuthSignatureMethod_HMAC_SHA1(),$test_consumer,$goog);
 
-		if ( $info['code'] == 200 )
-			return true;
-
-		return false;
+		$data = wp_remote_get($blog_req->to_url(), array('sslverify'=>false) );
+		
+		if ( wp_remote_retrieve_response_code( $data ) == 200 ) {
+			$response = wp_remote_retrieve_body( $data );
+		} else {
+			$response = false;
+		}
+		
+		return $response;
 	}
-
+	
 	function show_blogs($iter = 0) {
 		if ( empty($this->blogs) ) {
-			$headers = array(
-				"GET /feeds/default/blogs HTTP/1.0",
-				"Host: www.blogger.com",
-				"Authorization: AuthSub token=\"$this->token\""
-			);
-			$request = join( "\r\n", $headers ) . "\r\n\r\n";
-			$sock = $this->_get_blogger_sock( );
-			if ( ! $sock ) return;
-			$response = $this->_txrx( $sock, $request );
+			$xml = $this->oauth_get('https://www.blogger.com/feeds/default/blogs');
 
-			// Quick and dirty XML mining.
-			list( $headers, $xml ) = explode( "\r\n\r\n", $response );
-			$p = xml_parser_create();
-			xml_parse_into_struct($p, $xml, $vals, $index);
-			xml_parser_free($p);
-
-			$this->title = $vals[$index['TITLE'][0]]['value'];
-
 			// Give it a few retries... this step often flakes out the first time.
-			if ( empty( $index['ENTRY'] ) ) {
+			if ( empty( $xml ) ) {
 				if ( $iter < 3 ) {
 					return $this->show_blogs($iter + 1);
 				} else {
@@ -173,36 +210,40 @@
 					return false;
 				}
 			}
-
-			foreach ( $index['ENTRY'] as $i ) {
-				$blog = array();
-				while ( ( $tag = $vals[$i] ) && ! ( $tag['tag'] == 'ENTRY' && $tag['type'] == 'close' ) ) {
-					if ( $tag['tag'] == 'TITLE' ) {
-						$blog['title'] = $tag['value'];
-					} elseif ( $tag['tag'] == 'SUMMARY' ) {
-						$blog['summary'] = $tag['value'];
-					} elseif ( $tag['tag'] == 'LINK' ) {
-						if ( $tag['attributes']['REL'] == 'alternate' && $tag['attributes']['TYPE'] == 'text/html' ) {
-							$parts = parse_url( $tag['attributes']['HREF'] );
-							$blog['host'] = $parts['host'];
-						} elseif ( $tag['attributes']['REL'] == 'edit' ) {
-							$blog['gateway'] = $tag['attributes']['HREF'];
-						} elseif ( $tag['attributes']['REL'] == 'http://schemas.google.com/g/2005#post' ) {
-							$parts = parse_url( $tag['attributes']['HREF'] );
-							$blog['posts_host'] = $parts['host'];
-							$blog['posts_path'] = $parts['path'];
-						}
-					}
-					++$i;
-				}
+			
+			$feed = new SimplePie();
+			$feed->set_raw_data($xml);
+			$feed->init();
+			
+			foreach ($feed->get_items() as $item) {
+                $blog = array(); //reset
+				$blog['title'] = $item->get_title();
+				$blog['summary'] = $item->get_description();
+                
+                //ID is of the form tag:blogger.com,1999:blog-417730729915399755
+                //We need that number from the end
+                $rawid = explode('-',$item->get_id());
+                $blog['id'] = $rawid[count($rawid)-1];
+			
+				$parts = parse_url( $item->get_link( 0, 'alternate' ) );
+				$blog['host'] = $parts['host'];
+				$blog['gateway'] = $item->get_link( 0, 'edit' );
+				$blog['posts_url'] = $item->get_link( 0, 'http://schemas.google.com/g/2005#post' );
+                
+                //AGC:20/4/2012 Developers guide suggests that the correct feed is located as follows
+                //See https://developers.google.com/blogger/docs/1.0/developers_guide_php
+                $blog['comments_url'] =  "http://www.blogger.com/feeds/{$blog['id']}/comments/default";
+				
 				if ( ! empty ( $blog ) ) {
-					$blog['total_posts'] = $this->get_total_results('posts', $blog['host']);
-					$blog['total_comments'] = $this->get_total_results('comments', $blog['host']);
+					$blog['total_posts'] = $this->get_total_results( $blog['posts_url'] );
+                    $blog['total_comments'] = $this->get_total_results($blog['comments_url']);
+                    
 					$blog['mode'] = 'init';
 					$this->blogs[] = $blog;
 				}
+
 			}
-
+			
 			if ( empty( $this->blogs ) ) {
 				$this->uh_oh(
 					__('No blogs found', 'blogger-importer'),
@@ -210,8 +251,11 @@
 					''
 				);
 				return false;
-			}
+			}			
 		}
+        
+//Should probably be using WP_LIST_TABLE here rather than manually rendering a table in html
+//http://wpengineer.com/2426/wp_list_table-a-step-by-step-guide/        
 //echo '<pre>'.print_r($this,1).'</pre>';
 		$start    = esc_js( __('Import', 'blogger-importer') );
 		$continue = esc_js( __('Continue', 'blogger-importer') );
@@ -230,6 +274,8 @@
 		$noscript = __('This feature requires Javascript but it seems to be disabled. Please enable Javascript and then reload this page. Don&#8217;t worry, you can turn it back off when you&#8217;re done.', 'blogger-importer');
 
 		$interval = STATUS_INTERVAL * 1000;
+        $init = '';
+        $rows = '';
 
 		foreach ( $this->blogs as $i => $blog ) {
 			if ( $blog['mode'] == 'init' )
@@ -242,7 +288,7 @@
 			$blogtitle = esc_js( $blog['title'] );
 			$pdone = isset($blog['posts_done']) ? (int) $blog['posts_done'] : 0;
 			$cdone = isset($blog['comments_done']) ? (int) $blog['comments_done'] : 0;
-			$init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}'," . $this->get_js_status($i) . ');';
+			$init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}','" . $this->get_js_status($i) . '\');';
 			$pstat = "<div class='ind' id='pind$i'>&nbsp;</div><div id='pstat$i' class='stat'>$pdone/{$blog['total_posts']}</div>";
 			$cstat = "<div class='ind' id='cind$i'>&nbsp;</div><div id='cstat$i' class='stat'>$cdone/{$blog['total_comments']}</div>";
 			$rows .= "<tr id='blog$i'><td class='blogtitle'>$blogtitle</td><td class='bloghost'>{$blog['host']}</td><td class='bar'>$pstat</td><td class='bar'>$cstat</td><td class='submit'><input type='submit' class='button' id='submit$i' value='$value' /><input type='hidden' name='blog' value='$i' /></td></tr>\n";
@@ -258,7 +304,7 @@
 				this.blog   = i;
 				this.mode   = mode;
 				this.title  = title;
-				this.status = status;
+                eval('this.status='+status);
 				this.button = document.getElementById('submit'+this.blog);
 			};
 			blog.prototype = {
@@ -311,7 +357,10 @@
 				},
 				update: function() {
 					jQuery('#pind'+this.blog).width(((this.status.p1>0&&this.status.p2>0)?(this.status.p1/this.status.p2*jQuery('#pind'+this.blog).parent().width()):1)+'px');
+                    jQuery('#pstat'+this.blog).attr('title', 'Posts skipped '+this.status.p3);
 					jQuery('#cind'+this.blog).width(((this.status.c1>0&&this.status.c2>0)?(this.status.c1/this.status.c2*jQuery('#cind'+this.blog).parent().width()):1)+'px');
+                    jQuery('#cstat'+this.blog).attr('title', 'Comments skipped '+this.status.c3);
+
 				},
 				stop: function() {
 					this.cont = false;
@@ -372,53 +421,39 @@
 	function have_time() {
 		global $importer_started;
 		if ( time() - $importer_started > MAX_EXECUTION_TIME )
-			die('continue');
+			self::ajax_die('continue');
 		return true;
 	}
-
-	function get_total_results($type, $host) {
-		$headers = array(
-			"GET /feeds/$type/default?max-results=1&start-index=2 HTTP/1.0",
-			"Host: $host",
-			"Authorization: AuthSub token=\"$this->token\""
-		);
-		$request = join( "\r\n", $headers ) . "\r\n\r\n";
-		$sock = $this->_get_blogger_sock( $host );
-		if ( ! $sock ) return;
-		$response = $this->_txrx( $sock, $request );
-		$response = $this->parse_response( $response );
-		$parser = xml_parser_create();
-		xml_parse_into_struct($parser, $response['body'], $struct, $index);
-		xml_parser_free($parser);
-		$total_results = $struct[$index['OPENSEARCH:TOTALRESULTS'][0]]['value'];
-		return (int) $total_results;
+	
+	function get_total_results($url) {		
+		$response = $this->oauth_get( $url, array('max-results'=>1, 'start-index'=>2) );
+        
+        $feed = new SimplePie(); 
+        $feed->set_raw_data($response); 
+        $feed->init(); 
+        $results = $feed->get_channel_tags('http://a9.com/-/spec/opensearchrss/1.0/', 'totalResults'); 
+       
+        $total_results = $results[0]['data']; 
+        unset($feed); 
+        return (int) $total_results; 
 	}
 
 	function import_blog($blogID) {
-		global $importing_blog;
+ 		global $importing_blog;
 		$importing_blog = $blogID;
 
 		if ( isset($_GET['authors']) )
 			return print($this->get_author_form());
 
-		header('Content-Type: text/plain');
-
 		if ( isset($_GET['status']) )
-			die($this->get_js_status());
+			self::ajax_die($this->get_js_status());
 
 		if ( isset($_GET['saveauthors']) )
-			die($this->save_authors());
+			self::ajax_die($this->save_authors());
 
-		$blog = $this->blogs[$blogID];
-		$total_results = $this->get_total_results('posts', $blog['host']);
-		$this->blogs[$importing_blog]['total_posts'] = $total_results;
-
-		$start_index = $total_results - MAX_RESULTS + 1;
-
+        //Simpler counting for posts as we load them forwards
 		if ( isset( $this->blogs[$importing_blog]['posts_start_index'] ) )
 			$start_index = (int) $this->blogs[$importing_blog]['posts_start_index'];
-		elseif ( $total_results > MAX_RESULTS )
-			$start_index = $total_results - MAX_RESULTS + 1;
 		else
 			$start_index = 1;
 
@@ -426,125 +461,146 @@
 		if ( $start_index > 0 ) {
 			// Grab all the posts
 			$this->blogs[$importing_blog]['mode'] = 'posts';
-			$query = "start-index=$start_index&max-results=" . MAX_RESULTS;
 			do {
+			 
 				$index = $struct = $entries = array();
-				$headers = array(
-					"GET {$blog['posts_path']}?$query HTTP/1.0",
-					"Host: {$blog['posts_host']}",
-					"Authorization: AuthSub token=\"$this->token\""
-				);
-				$request = join( "\r\n", $headers ) . "\r\n\r\n";
-				$sock = $this->_get_blogger_sock( $blog['posts_host'] );
-				if ( ! $sock ) return; // TODO: Error handling
-				$response = $this->_txrx( $sock, $request );
 
-				$response = $this->parse_response( $response );
+				$url = $this->blogs[$importing_blog]['posts_url'];
 
-				// Extract the entries and send for insertion
-				preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches );
-				if ( count( $matches[0] ) ) {
-					$entries = array_reverse($matches[0]);
-					foreach ( $entries as $entry ) {
-						$entry = "<feed>$entry</feed>";
-						$AtomParser = new AtomParser();
-						$AtomParser->parse( $entry );
-						$result = $this->import_post($AtomParser->entry);
-						if ( is_wp_error( $result ) )
-							return $result;
-						unset($AtomParser);
+				$response = $this->oauth_get( $url, array('max-results'=>MAX_RESULTS, 'start-index'=>$start_index) );
+				
+				if ($response == false) break;
+								
+				// parse the feed
+				$feed = new SimplePie();
+                $feed->set_item_class('WP_SimplePie_Blog_Item');
+                $feed->set_sanitize_class('Blogger_Importer_Sanitize');
+                $feed->set_raw_data($response);
+				$feed->init();
+                
+				foreach ( $feed->get_items() as $item ) {
+				    
+					$blogentry = new BloggerEntry();
+					
+					$blogentry->id = $item->get_id();
+					$blogentry->published = $item->get_published();
+					$blogentry->updated = $item->get_updated();
+                    $blogentry->isDraft = $item->get_draft_status($item);
+					$blogentry->title = $item->get_title();
+					$blogentry->content = $item->get_content();
+					$blogentry->author = $item->get_author()->get_name();
+					
+					$linktypes = array('replies','edit','self','alternate');
+					foreach ($linktypes as $type) {
+						$links = $item->get_links($type);
+                        
+                        if (!is_null($links)) {
+    						foreach ($links as $link) {
+    							$blogentry->links[] = array( 'rel' => $type, 'href' => $link );
+    						}
+                        }
 					}
-				} else break;
-
-				// Get the 'previous' query string which we'll use on the next iteration
-				$query = '';
-				$links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches);
-				if ( count( $matches[1] ) )
-					foreach ( $matches[1] as $match )
-						if ( preg_match('/rel=.previous./', $match) )
-							$query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') );
-
-				if ( $query ) {
-					parse_str($query, $q);
-					$this->blogs[$importing_blog]['posts_start_index'] = (int) $q['start-index'];
-				} else
-					$this->blogs[$importing_blog]['posts_start_index'] = 0;
+					
+					$cats = $item->get_categories();
+                    
+                    if (!is_null($cats)) {
+    					foreach ($cats as $cat) {
+    						$blogentry->categories[] = $cat->term;
+    					}
+                    }
+					
+					$result = $this->import_post($blogentry);
+                    
+                    //Ref: Not importing properly http://core.trac.wordpress.org/ticket/19096  
+                    //Simplified this section to count what is loaded rather than parsing the results again
+                    $start_index++;
+				}
+				
+                $this->blogs[$importing_blog]['posts_start_index'] = $start_index;
+                
 				$this->save_vars();
-			} while ( !empty( $query ) && $this->have_time() );
+                
+			} while ( $this->blogs[$importing_blog]['total_posts'] >$start_index && $this->have_time() ); //have time function will "die" if it's out of time
 		}
 
-		$total_results = $this->get_total_results( 'comments', $blog['host'] );
-		$this->blogs[$importing_blog]['total_comments'] = $total_results;
-
-		if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) )
+        
+        if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) )
 			$start_index = (int) $this->blogs[$importing_blog]['comments_start_index'];
-		elseif ( $total_results > MAX_RESULTS )
-			$start_index = $total_results - MAX_RESULTS + 1;
-		else
-			$start_index = 1;
+        else
+            $start_index = 1; 
+        
+		if ( $start_index > 0 && $this->blogs[$importing_blog]['total_comments'] > 0) {
 
-		if ( $start_index > 0 ) {
-			// Grab all the comments
 			$this->blogs[$importing_blog]['mode'] = 'comments';
-			$query = "start-index=$start_index&max-results=" . MAX_RESULTS;
 			do {
 				$index = $struct = $entries = array();
-				$headers = array(
-					"GET /feeds/comments/default?$query HTTP/1.0",
-					"Host: {$blog['host']}",
-					"Authorization: AuthSub token=\"$this->token\""
-				);
-				$request = join( "\r\n", $headers ) . "\r\n\r\n";
-				$sock = $this->_get_blogger_sock( $blog['host'] );
-				if ( ! $sock ) return; // TODO: Error handling
-				$response = $this->_txrx( $sock, $request );
 
-				$response = $this->parse_response( $response );
-
-				// Extract the comments and send for insertion
-				preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches );
-				if ( count( $matches[0] ) ) {
-					$entries = array_reverse( $matches[0] );
-					foreach ( $entries as $entry ) {
-						$entry = "<feed>$entry</feed>";
-						$AtomParser = new AtomParser();
-						$AtomParser->parse( $entry );
-						$this->import_comment($AtomParser->entry);
-						unset($AtomParser);
-					}
-				}
-
-				// Get the 'previous' query string which we'll use on the next iteration
-				$query = '';
-				$links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches);
-				if ( count( $matches[1] ) )
-					foreach ( $matches[1] as $match )
-						if ( preg_match('/rel=.previous./', $match) )
-							$query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') );
-
-				parse_str($query, $q);
-
-				$this->blogs[$importing_blog]['comments_start_index'] = (int) $q['start-index'];
+                //So we can link up the comments as we go we need to load them in reverse order 
+                //Reverse the start index as the GData Blogger feed can't be sorted 
+                $batch = ((floor(($this->blogs[$importing_blog]['total_comments'] - $start_index)/MAX_RESULTS)*MAX_RESULTS)+1);
+                                              
+				$response = $this->oauth_get( $this->blogs[$importing_blog]['comments_url'], array('max-results'=>MAX_RESULTS, 'start-index'=>$batch) );
+				
+				// parse the feed
+				$feed = new SimplePie();
+                $feed->set_item_class('WP_SimplePie_Blog_Item');
+                // Use the standard "stricter" sanitize class for comments
+				$feed->set_raw_data($response);
+				$feed->init();
+                
+                //Reverse the batch so we load the oldest comments first and hence can link up nested comments
+                $comments = array_reverse($feed->get_items());
+                
+                if (!is_null($comments)) {
+    				foreach ($comments  as $item ) {
+  
+    					$blogentry = new BloggerEntry();
+                        $blogentry->id = $item->get_id(); 
+    					$blogentry->updated = $item->get_updated();
+    					$blogentry->content = $item->get_content();
+    					$blogentry->author = $item->get_author()->get_name();
+    					$blogentry->authoruri = $item->get_author()->get_link();
+    					$blogentry->authoremail = $item->get_author()->get_email();
+    
+    					$temp = $item->get_item_tags('http://purl.org/syndication/thread/1.0','in-reply-to');
+    
+    					foreach ($temp as $t) {
+    						if ( isset( $t['attribs']['']['source'] ) ) {
+    							$blogentry->source = $t['attribs']['']['source'];
+    						}
+    					}
+                        
+                        //Get the links
+                        $linktypes = array('edit','self','alternate','related');
+    					foreach ($linktypes as $type) {
+    						$links = $item->get_links($type);
+                            if (!is_null($links)) {
+        						foreach ($links as $link) {
+        							$blogentry->links[] = array( 'rel' => $type, 'href' => $link );
+        						}
+                            }
+    					}
+    					
+    					$this->import_comment($blogentry);
+                        $start_index++;
+    				}
+                }
+				
+				$this->blogs[$importing_blog]['comments_start_index'] = $start_index;
 				$this->save_vars();
-			} while ( !empty( $query ) && $this->have_time() );
+			} while ( $this->blogs[$importing_blog]['total_comments'] > $start_index  && $this->have_time() );
 		}
+
 		$this->blogs[$importing_blog]['mode'] = 'authors';
 		$this->save_vars();
+
 		if ( !$this->blogs[$importing_blog]['posts_done'] && !$this->blogs[$importing_blog]['comments_done'] )
-			die('nothing');
+			self::ajax_die('nothing');
+
 		do_action('import_done', 'blogger');
-		die('done');
+		self::ajax_die('done');
 	}
 
-	function convert_date( $date ) {
-	    preg_match('#([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.[0-9]+)?(Z|[\+|\-][0-9]{2,4}){0,1}#', $date, $date_bits);
-	    $offset = iso8601_timezone_to_offset( $date_bits[7] );
-		$timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]);
-		$timestamp -= $offset; // Convert from Blogger local time to GMT
-		$timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time
-		return gmdate('Y-m-d H:i:s', $timestamp);
-	}
-
 	function no_apos( $string ) {
 		return str_replace( '&apos;', "'", $string);
 	}
@@ -559,36 +615,42 @@
 
 	function import_post( $entry ) {
 		global $importing_blog;
-
-		// The old permalink is all Blogger gives us to link comments to their posts.
-		if ( isset( $entry->draft ) )
-			$rel = 'self';
-		else
-			$rel = 'alternate';
-		foreach ( $entry->links as $link ) {
-			if ( $link['rel'] == $rel ) {
+        
+		foreach ( $entry->links as $link ) {			
+			// save the self link as meta
+			if ( $link['rel'] == 'self' ) {
+				$postself = $link['href'];
 				$parts = parse_url( $link['href'] );
 				$entry->old_permalink = $parts['path'];
-				break;
 			}
+
+            // get the old URI for the page when available
+            if ( $link['rel'] == 'alternate' ) {
+				$parts = parse_url( $link['href'] );
+				$entry->bookmark = $parts['path'];
+			}
+            
+			// save the replies feed link as meta (ignore the comment form one)
+			if ( $link['rel'] == 'replies' && false === strpos($link['href'], '#comment-form') ) {
+				$postreplies = $link['href'];
+			}
 		}
-
-		$post_date    = $this->convert_date( $entry->published );
+		
+        //Check if we are double cleaning here? Does the Simplepie already do all this?
+		$post_date    =  $entry->published;
 		$post_content = trim( addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) ) );
 		$post_title   = trim( addslashes( $this->no_apos( $this->min_whitespace( $entry->title ) ) ) );
-		$post_status  = isset( $entry->draft ) ? 'draft' : 'publish';
+        
+		$post_status  = $entry->isDraft ? 'draft' : 'publish';
 
-		// Clean up content
-		$post_content = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_content);
-		$post_content = str_replace('<br>', '<br />', $post_content);
-		$post_content = str_replace('<hr>', '<hr />', $post_content);
+		// N.B. Clean up of $post_content is now part of the sanitize class
 
 		// Checks for duplicates
 		if ( isset( $this->blogs[$importing_blog]['posts'][$entry->old_permalink] ) ) {
-			++$this->blogs[$importing_blog]['posts_skipped'];
+			$this->blogs[$importing_blog]['posts_skipped']++;
 		} elseif ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) {
 			$this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
-			++$this->blogs[$importing_blog]['posts_skipped'];
+			$this->blogs[$importing_blog]['posts_skipped']++;
 		} else {
 			$post = compact('post_date', 'post_content', 'post_title', 'post_status');
 
@@ -602,10 +664,20 @@
 
 			add_post_meta( $post_id, 'blogger_blog', $this->blogs[$importing_blog]['host'], true );
 			add_post_meta( $post_id, 'blogger_author', $author, true );
-			add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true );
+            
+            //Use the page id if available or the blogger internal id if it's a draft
+	        if ( $entry->isDraft | !isset($entry->bookmark) )
+			     add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true );
+            else
+			     add_post_meta( $post_id, 'blogger_permalink', $entry->bookmark, true );
+                 
+			add_post_meta( $post_id, '_blogger_self', $postself, true );
+            //Undefined variable: postreplies 
+			//add_post_meta( $post_id, '_blogger_replies', $postreplies, true );
 
 			$this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
-			++$this->blogs[$importing_blog]['posts_done'];
+            
+			$this->blogs[$importing_blog]['posts_done']++;
 		}
 		$this->save_vars();
 		return;
@@ -613,59 +685,93 @@
 
 	function import_comment( $entry ) {
 		global $importing_blog;
-
+		
+		$parts = parse_url( $entry->source );
+		$entry->old_post_permalink = $parts['path'];          //Will be something like this '/feeds/417730729915399755/posts/default/8397846992898424746'
+		
 		// Drop the #fragment and we have the comment's old post permalink.
 		foreach ( $entry->links as $link ) {
 			if ( $link['rel'] == 'alternate' ) {
 				$parts = parse_url( $link['href'] );
 				$entry->old_permalink = $parts['fragment'];
-				$entry->old_post_permalink = $parts['path'];
-				break;
 			}
+            //Parent post for nested links
+            if ( $link['rel'] == 'related' ) {
+                $parts = parse_url( $link['href'] );
+				$entry->related = $parts['path'];
+            }
+            if ( $link['rel'] == 'self' ) {
+                $parts = parse_url( $link['href'] );
+				$entry->self = $parts['path'];
+            }            
 		}
+        
+        //Check for duplicated cleanup here
+    	$comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink];
+		$comment_author  = addslashes( $this->no_apos( strip_tags( $entry->author ) ) );
+		$comment_author_url = addslashes( $this->no_apos( strip_tags( $entry->authoruri ) ) );
+		$comment_author_email = addslashes( $this->no_apos( strip_tags( $entry->authoremail ) ) );
+		$comment_date    = $entry->updated;
 
-		$comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink];
-		preg_match('#<name>(.+?)</name>.*(?:\<uri>(.+?)</uri>)?#', $entry->author, $matches);
-		$comment_author  = addslashes( $this->no_apos( strip_tags( (string) $matches[1] ) ) );
-		$comment_author_url = addslashes( $this->no_apos( strip_tags( (string) $matches[2] ) ) );
-		$comment_date    = $this->convert_date( $entry->updated );
+		// Clean up content
+        // Again, check if the Simplepie is already handling all this stuff
 		$comment_content = addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) );
-
-		// Clean up content
 		$comment_content = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $comment_content);
 		$comment_content = str_replace('<br>', '<br />', $comment_content);
 		$comment_content = str_replace('<hr>', '<hr />', $comment_content);
-
-		// Checks for duplicates
-		if (
-			isset( $this->blogs[$importing_blog]['comments'][$entry->old_permalink] ) ||
-			comment_exists( $comment_author, $comment_date )
-		) {
-			++$this->blogs[$importing_blog]['comments_skipped'];
-		} else {
-			$comment = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_date', 'comment_content');
-
-			$comment = wp_filter_comment($comment);
-			$comment_id = wp_insert_comment($comment);
-
-			$this->blogs[$importing_blog]['comments'][$entry->old_permalink] = $comment_id;
-
-			++$this->blogs[$importing_blog]['comments_done'];
-		}
+        
+        // Nested comment?
+        if (!is_null($entry->related)) {
+            $comment_parent = $this->blogs[$importing_blog]['comments'][$entry->related];
+        }
+        
+        // if the post does not exist then we need stop and not add the comment
+        if ($comment_post_ID != 0) {
+    		// Checks for duplicates
+    		if (
+    			isset( $this->blogs[$importing_blog][$entry->id] ) ||
+    			$this->comment_exists($comment_post_ID, $comment_author, $comment_date )
+    		) {               
+    			$this->blogs[$importing_blog]['comments_skipped']++;
+    		} else {
+    			$comment = compact('comment_post_ID', 'comment_author', 'comment_author_url','comment_author_email', 'comment_date', 'comment_content','comment_parent');
+    
+    			$comment = wp_filter_comment($comment);
+    			$comment_id = wp_insert_comment($comment);
+                
+    			$this->blogs[$importing_blog]['comments'][$entry->id] = $comment_id;
+                $this->blogs[$importing_blog]['comments'][$entry->self] = $comment_id; //For nested comments
+    
+    			$this->blogs[$importing_blog]['comments_done']++;
+    		}
+        }
+        else {
+            $this->blogs[$importing_blog]['comments_skipped']++;
+        }
 		$this->save_vars();
 	}
+    
+    function ajax_die($data){
+        ob_clean(); //Discard any debug messages or other fluff already sent
+       	header('Content-Type: text/plain');
+		die($data);
+    }
 
+
 	function get_js_status($blog = false) {
 		global $importing_blog;
 		if ( $blog === false )
 			$blog = $this->blogs[$importing_blog];
 		else
 			$blog = $this->blogs[$blog];
+            
 		$p1 = isset( $blog['posts_done'] ) ? (int) $blog['posts_done'] : 0;
 		$p2 = isset( $blog['total_posts'] ) ? (int) $blog['total_posts'] : 0;
+        $p3 = isset( $blog['posts_skipped'] ) ? (int) $blog['posts_skipped'] : 0;
 		$c1 = isset( $blog['comments_done'] ) ? (int) $blog['comments_done'] : 0;
 		$c2 = isset( $blog['total_comments'] ) ? (int) $blog['total_comments'] : 0;
-		return "{p1:$p1,p2:$p2,c1:$c1,c2:$c2}";
+        $c3 = isset( $blog['comments_skipped'] ) ? (int) $blog['comments_skipped'] : 0;
+        return "{p1:$p1,p2:$p2,p3:$p3,c1:$c1,c2:$c2,c3:$c3}";
 	}
 
 	function get_author_form($blog = false) {
@@ -688,6 +794,7 @@
 		$mapthis = __('Blogger username', 'blogger-importer');
 		$tothis = __('WordPress login', 'blogger-importer');
 		$submit = esc_js( __('Save Changes', 'blogger-importer') );
+        $rows = '';
 
 		foreach ( $blog['authors'] as $i => $author )
 			$rows .= "<tr><td><label for='authors[$i]'>{$author[0]}</label></td><td><select name='authors[$i]' id='authors[$i]'>" . $this->get_user_options($author[1]) . "</select></td></tr>";
@@ -698,11 +805,13 @@
 	function get_user_options($current) {
 		global $importer_users;
 		if ( ! isset( $importer_users ) )
-			$importer_users = (array) get_users_of_blog();
+            $importer_users = (array) get_users();  //Function: get_users_of_blog() Deprecated in version 3.1. Use get_users() instead.
 
+        $options = '';
+
 		foreach ( $importer_users as $user ) {
-			$sel = ( $user->user_id == $current ) ? " selected='selected'" : '';
-			$options .= "<option value='$user->user_id'$sel>$user->display_name</option>";
+			$sel = ( $user->ID == $current ) ? " selected='selected'" : '';
+			$options .= "<option value='$user->ID'$sel>$user->display_name</option>";
 		}
 
 		return $options;
@@ -710,9 +819,11 @@
 
 	function save_authors() {
 		global $importing_blog, $wpdb;
+        $blog =& $this->blogs[$importing_blog]; //Get a reference to blogs so we don't have to write it longhand
+		        
 		$authors = (array) $_POST['authors'];
 
-		$host = $this->blogs[$importing_blog]['host'];
+		$host = $blog['host'];
 
 		// Get an array of posts => authors
 		$post_ids = (array) $wpdb->get_col( $wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'blogger_blog' AND meta_value = %s", $host) );
@@ -725,101 +836,30 @@
 			$user_id = (int) $user_id;
 
 			// Skip authors that haven't been changed
-			if ( $user_id == $this->blogs[$importing_blog]['authors'][$author][1] )
+			if ( $user_id == $blog['authors'][$author][1] )
 				continue;
 
 			// Get a list of the selected author's posts
-			$post_ids = (array) array_keys( $authors_posts, $this->blogs[$importing_blog]['authors'][$author][0] );
+			$post_ids = (array) array_keys( $authors_posts, $blog['authors'][$author][0] );
 			$post_ids = join( ',', $post_ids);
 
 			$wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET post_author = %d WHERE id IN ($post_ids)", $user_id) );
-			$this->blogs[$importing_blog]['authors'][$author][1] = $user_id;
+			$blog['authors'][$author][1] = $user_id;
 		}
 		$this->save_vars();
 
 		wp_redirect('edit.php');
 	}
 
-	function _get_auth_sock() {
-		// Connect to https://www.google.com
-		if ( !$sock = @ fsockopen('ssl://www.google.com', 443, $errno, $errstr) ) {
-			$this->uh_oh(
-				__('Could not connect to https://www.google.com', 'blogger-importer'),
-				__('There was a problem opening a secure connection to Google. This is what went wrong:', 'blogger-importer'),
-				"$errstr ($errno)"
-			);
-			return false;
-		}
-		return $sock;
-	}
-
-	function _get_blogger_sock($host = 'www2.blogger.com') {
-		if ( !$sock = @ fsockopen($host, 80, $errno, $errstr) ) {
-			$this->uh_oh(
-				sprintf( __('Could not connect to %s', 'blogger-importer'), $host ),
-				__('There was a problem opening a connection to Blogger. This is what went wrong:', 'blogger-importer'),
-				"$errstr ($errno)"
-			);
-			return false;
-		}
-		return $sock;
-	}
-
-	function _txrx( $sock, $request ) {
-		fwrite( $sock, $request );
-		while ( ! feof( $sock ) )
-			$response .= @ fread ( $sock, 8192 );
-		fclose( $sock );
-		return $response;
-	}
-
-	function revoke($token) {
-		$headers = array(
-			"GET /accounts/AuthSubRevokeToken HTTP/1.0",
-			"Authorization: AuthSub token=\"$token\""
-		);
-		$request = join( "\r\n", $headers ) . "\r\n\r\n";
-		$sock = $this->_get_auth_sock( );
-		if ( ! $sock ) return false;
-		$this->_txrx( $sock, $request );
-	}
-
 	function restart() {
 		global $wpdb;
 		$options = get_option( 'blogger_importer' );
 
-		if ( isset( $options['token'] ) )
-			$this->revoke( $options['token'] );
-
 		delete_option('blogger_importer');
 		$wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'blogger_author'");
 		wp_redirect('?import=blogger');
 	}
 
-	// Returns associative array of code, header, cookies, body. Based on code from php.net.
-	function parse_response($this_response) {
-		// Split response into header and body sections
-		list($response_headers, $response_body) = explode("\r\n\r\n", $this_response, 2);
-		$response_header_lines = explode("\r\n", $response_headers);
-
-		// First line of headers is the HTTP response code
-		$http_response_line = array_shift($response_header_lines);
-		if (preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@',$http_response_line, $matches)) { $response_code = $matches[1]; }
-
-		// put the rest of the headers in an array
-		$response_header_array = array();
-		foreach($response_header_lines as $header_line) {
-			list($header,$value) = explode(': ', $header_line, 2);
-			$response_header_array[$header] .= $value."\n";
-		}
-
-		$cookie_array = array();
-		$cookies = explode("\n", $response_header_array["Set-Cookie"]);
-		foreach($cookies as $this_cookie) { array_push($cookie_array, "Cookie: ".$this_cookie); }
-
-		return array("code" => $response_code, "header" => $response_header_array, "cookies" => $cookie_array, "body" => $response_body);
-	}
-
 	// Step 9: Congratulate the user
 	function congrats() {
 		$blog = (int) $_GET['blog'];
@@ -836,7 +876,7 @@
 	function start() {
 		if ( isset($_POST['restart']) )
 			$this->restart();
-
+			
 		$options = get_option('blogger_importer');
 
 		if ( is_array($options) )
@@ -849,9 +889,9 @@
 			$result = $this->import_blog( $blog );
 			if ( is_wp_error( $result ) )
 				echo $result->get_error_message();
-		} elseif ( isset($_GET['token']) )
+		} elseif ( isset($_GET['token']) && isset($_GET['token_secret']) )
 			$this->auth();
-		elseif ( isset($this->token) && $this->token_is_valid() )
+		elseif ( isset($this->token) && isset($this->token_secret) )
 			$this->show_blogs();
 		else
 			$this->greet();
@@ -873,232 +913,32 @@
 		return !empty($vars);
 	}
 
-	function admin_head() {
-?>
-<style type="text/css">
-td { text-align: center; line-height: 2em;}
-thead td { font-weight: bold; }
-.bar {
-	width: 200px;
-	text-align: left;
-	line-height: 2em;
-	padding: 0px;
-}
-.ind {
-	position: absolute;
-	background-color: #83B4D8;
-	width: 1px;
-	z-index: 9;
-}
-.stat {
-	z-index: 10;
-	position: relative;
-	text-align: center;
-}
-</style>
-<?php
-	}
+    function comment_exists($post_id, $comment_author, $comment_date) {
+    //Do we have 2 comments for the same author at the same time, on the same post?
+    //returns comment id
+	global $wpdb;
 
-	function Blogger_Import() {
-		global $importer_started;
-		$importer_started = time();
-		if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) {
-			wp_enqueue_script('jquery');
-			add_action('admin_head', array(&$this, 'admin_head'));
-		}
-	}
-}
+	$comment_author = stripslashes($comment_author);
+	$comment_date = stripslashes($comment_date);
 
-$blogger_import = new Blogger_Import();
+	return $wpdb->get_var( $wpdb->prepare("SELECT comment_ID FROM $wpdb->comments
+			WHERE comment_post_ID = %s and comment_author = %s AND comment_date = %s", $post_id, $comment_author, $comment_date) );
+    }
 
-register_importer('blogger', __('Blogger', 'blogger-importer'), __('Import posts, comments, and users from a Blogger blog.', 'blogger-importer'), array ($blogger_import, 'start'));
+}
 
-class AtomEntry {
+class BloggerEntry {
 	var $links = array();
 	var $categories = array();
 }
 
-class AtomParser {
-
-	var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights');
-	var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft','author');
-
-	var $depth = 0;
-	var $indent = 2;
-	var $in_content;
-	var $ns_contexts = array();
-	var $ns_decls = array();
-	var $is_xhtml = false;
-	var $skipped_div = false;
-
-	var $entry;
-
-	function AtomParser() {
-		$this->entry = new AtomEntry();
-	}
-
-	function _map_attrs_func( $k, $v ) {
-		return "$k=\"$v\"";
-	}
-
-	function _map_xmlns_func( $p, $n ) {
-		$xd = "xmlns";
-		if ( strlen( $n[0] ) > 0 )
-			$xd .= ":{$n[0]}";
-
-		return "{$xd}=\"{$n[1]}\"";
-	}
-
-	function parse($xml) {
-
-		global $app_logging;
-		array_unshift($this->ns_contexts, array());
-
-		$parser = xml_parser_create_ns();
-		xml_set_object($parser, $this);
-		xml_set_element_handler($parser, "start_element", "end_element");
-		xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);
-		xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0);
-		xml_set_character_data_handler($parser, "cdata");
-		xml_set_default_handler($parser, "_default");
-		xml_set_start_namespace_decl_handler($parser, "start_ns");
-		xml_set_end_namespace_decl_handler($parser, "end_ns");
-
-		$contents = "";
-
-		xml_parse($parser, $xml);
-
-		xml_parser_free($parser);
-
-		return true;
-	}
-
-	function start_element($parser, $name, $attrs) {
-
-		$tag = array_pop(split(":", $name));
-
-		array_unshift($this->ns_contexts, $this->ns_decls);
-
-		$this->depth++;
-
-		if (!empty($this->in_content)) {
-			$attrs_prefix = array();
-
-			// resolve prefixes for attributes
-			foreach($attrs as $key => $value) {
-				$attrs_prefix[$this->ns_to_prefix($key)] = $this->xml_escape($value);
-			}
-			$attrs_str = join(' ', array_map( array( &$this, '_map_attrs_func' ), array_keys($attrs_prefix), array_values($attrs_prefix)));
-			if (strlen($attrs_str) > 0) {
-				$attrs_str = " " . $attrs_str;
-			}
-
-			$xmlns_str = join(' ', array_map( array( &$this, '_map_xmlns_func' ), array_keys($this->ns_contexts[0]), array_values($this->ns_contexts[0])));
-			if (strlen($xmlns_str) > 0) {
-				$xmlns_str = " " . $xmlns_str;
-			}
-
-			// handle self-closing tags (case: a new child found right-away, no text node)
-			if (count($this->in_content) == 2) {
-				array_push($this->in_content, ">");
-			}
-
-			array_push($this->in_content, "<". $this->ns_to_prefix($name) ."{$xmlns_str}{$attrs_str}");
-		} else if (in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) {
-			$this->in_content = array();
-			$this->is_xhtml = $attrs['type'] == 'xhtml';
-			array_push($this->in_content, array($tag,$this->depth));
-		} else if ($tag == 'link') {
-			array_push($this->entry->links, $attrs);
-		} else if ($tag == 'category') {
-			array_push($this->entry->categories, $attrs['term']);
-		}
-
-		$this->ns_decls = array();
-	}
-
-	function end_element($parser, $name) {
-
-		$tag = array_pop(split(":", $name));
-
-		if (!empty($this->in_content)) {
-			if ($this->in_content[0][0] == $tag &&
-			$this->in_content[0][1] == $this->depth) {
-				array_shift($this->in_content);
-				if ($this->is_xhtml) {
-					$this->in_content = array_slice($this->in_content, 2, count($this->in_content)-3);
-				}
-				$this->entry->$tag = join('',$this->in_content);
-				$this->in_content = array();
-			} else {
-				$endtag = $this->ns_to_prefix($name);
-				if (strpos($this->in_content[count($this->in_content)-1], '<' . $endtag) !== false) {
-					array_push($this->in_content, "/>");
-				} else {
-					array_push($this->in_content, "</$endtag>");
-				}
-			}
-		}
-
-		array_shift($this->ns_contexts);
-
-		#print str_repeat(" ", $this->depth * $this->indent) . "end_element('$name')" ."\n";
-
-		$this->depth--;
-	}
-
-	function start_ns($parser, $prefix, $uri) {
-		#print str_repeat(" ", $this->depth * $this->indent) . "starting: " . $prefix . ":" . $uri . "\n";
-		array_push($this->ns_decls, array($prefix,$uri));
-	}
-
-	function end_ns($parser, $prefix) {
-		#print str_repeat(" ", $this->depth * $this->indent) . "ending: #" . $prefix . "#\n";
-	}
-
-	function cdata($parser, $data) {
-		#print str_repeat(" ", $this->depth * $this->indent) . "data: #" . $data . "#\n";
-		if (!empty($this->in_content)) {
-			// handle self-closing tags (case: text node found, need to close element started)
-			if (strpos($this->in_content[count($this->in_content)-1], '<') !== false) {
-				array_push($this->in_content, ">");
-			}
-			array_push($this->in_content, $this->xml_escape($data));
-		}
-	}
-
-	function _default($parser, $data) {
-		# when does this gets called?
-	}
-
-
-	function ns_to_prefix($qname) {
-		$components = split(":", $qname);
-		$name = array_pop($components);
-
-		if (!empty($components)) {
-			$ns = join(":",$components);
-			foreach ($this->ns_contexts as $context) {
-				foreach ($context as $mapping) {
-					if ($mapping[1] == $ns && strlen($mapping[0]) > 0) {
-						return "$mapping[0]:$name";
-					}
-				}
-			}
-		}
-		return $name;
-	}
-
-	function xml_escape($string)
-	{
-			 return str_replace(array('&','"',"'",'<','>'),
-				array('&amp;','&quot;','&apos;','&lt;','&gt;'),
-				$string );
-	}
-}
 } // class_exists( 'WP_Importer' )
 
 function blogger_importer_init() {
     load_plugin_textdomain( 'blogger-importer', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
+    
+    $blogger_import = new Blogger_Import();
+    register_importer('blogger', __('Blogger', 'blogger-importer'), __('Import categories, posts and comments then maps users from a Blogger blog.', 'blogger-importer'), array ($blogger_import, 'start'));
+
 }
-add_action( 'init', 'blogger_importer_init' );
+add_action( 'admin_init', 'blogger_importer_init' );
Index: oauth.php
===================================================================
--- oauth.php	(revision 0)
+++ oauth.php	(working copy)
@@ -0,0 +1,926 @@
+<?php
+/*
+Original File from: http://oauth.googlecode.com/svn/code/php/
+License: MIT License:
+
+The MIT License
+
+Copyright (c) 2007 Andy Smith
+
+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:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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.
+
+
+Modified for use in WordPress by Otto
+
+Changes: All classes renamed to Blogger_* to prevent conflicts with other plugins/code using the same OAuth library.
+
+*/
+// vim: foldmethod=marker
+
+/* Generic exception class
+ */
+class Blogger_OAuthException extends Exception {
+  // pass
+}
+
+class Blogger_OAuthConsumer {
+  public $key;
+  public $secret;
+
+  function __construct($key, $secret, $callback_url=NULL) {
+    $this->key = $key;
+    $this->secret = $secret;
+    $this->callback_url = $callback_url;
+  }
+
+  function __toString() {
+    return "Blogger_OAuthConsumer[key=$this->key,secret=$this->secret]";
+  }
+}
+
+class Blogger_OAuthToken {
+  // access tokens and request tokens
+  public $key;
+  public $secret;
+
+  /**
+   * key = the token
+   * secret = the token secret
+   */
+  function __construct($key, $secret) {
+    $this->key = $key;
+    $this->secret = $secret;
+  }
+
+  /**
+   * generates the basic string serialization of a token that a server
+   * would respond to request_token and access_token calls with
+   */
+  function to_string() {
+    return "oauth_token=" .
+           Blogger_OAuthUtil::urlencode_rfc3986($this->key) .
+           "&oauth_token_secret=" .
+           Blogger_OAuthUtil::urlencode_rfc3986($this->secret);
+  }
+
+  function __toString() {
+    return $this->to_string();
+  }
+}
+
+/**
+ * A class for implementing a Signature Method
+ * See section 9 ("Signing Requests") in the spec
+ */
+abstract class Blogger_OAuthSignatureMethod {
+  /**
+   * Needs to return the name of the Signature Method (ie HMAC-SHA1)
+   * @return string
+   */
+  abstract public function get_name();
+
+  /**
+   * Build up the signature
+   * NOTE: The output of this function MUST NOT be urlencoded.
+   * the encoding is handled in Blogger_OAuthRequest when the final
+   * request is serialized
+   * @param Blogger_OAuthRequest $request
+   * @param Blogger_OAuthConsumer $consumer
+   * @param Blogger_OAuthToken $token
+   * @return string
+   */
+  abstract public function build_signature($request, $consumer, $token);
+
+  /**
+   * Verifies that a given signature is correct
+   * @param Blogger_OAuthRequest $request
+   * @param Blogger_OAuthConsumer $consumer
+   * @param Blogger_OAuthToken $token
+   * @param string $signature
+   * @return bool
+   */
+  public function check_signature($request, $consumer, $token, $signature) {
+    $built = $this->build_signature($request, $consumer, $token);
+
+    // Check for zero length, although unlikely here
+    if (strlen($built) == 0 || strlen($signature) == 0) {
+      return false;
+    }
+
+    if (strlen($built) != strlen($signature)) {
+      return false;
+    }
+
+    // Avoid a timing leak with a (hopefully) time insensitive compare
+    $result = 0;
+    for ($i = 0; $i < strlen($signature); $i++) {
+      $result |= ord($built{$i}) ^ ord($signature{$i});
+    }
+
+    return $result == 0;
+  }
+}
+
+/**
+ * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] 
+ * where the Signature Base String is the text and the key is the concatenated values (each first 
+ * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' 
+ * character (ASCII code 38) even if empty.
+ *   - Chapter 9.2 ("HMAC-SHA1")
+ */
+class Blogger_OAuthSignatureMethod_HMAC_SHA1 extends Blogger_OAuthSignatureMethod {
+  function get_name() {
+    return "HMAC-SHA1";
+  }
+
+  public function build_signature($request, $consumer, $token) {
+    $base_string = $request->get_signature_base_string();
+    $request->base_string = $base_string;
+
+    $key_parts = array(
+      $consumer->secret,
+      ($token) ? $token->secret : ""
+    );
+
+    $key_parts = Blogger_OAuthUtil::urlencode_rfc3986($key_parts);
+    $key = implode('&', $key_parts);
+
+    return base64_encode(hash_hmac('sha1', $base_string, $key, true));
+  }
+}
+
+/**
+ * The PLAINTEXT method does not provide any security protection and SHOULD only be used 
+ * over a secure channel such as HTTPS. It does not use the Signature Base String.
+ *   - Chapter 9.4 ("PLAINTEXT")
+ */
+class Blogger_OAuthSignatureMethod_PLAINTEXT extends Blogger_OAuthSignatureMethod {
+  public function get_name() {
+    return "PLAINTEXT";
+  }
+
+  /**
+   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and 
+   * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is 
+   * empty. The result MUST be encoded again.
+   *   - Chapter 9.4.1 ("Generating Signatures")
+   *
+   * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
+   * Blogger_OAuthRequest handles this!
+   */
+  public function build_signature($request, $consumer, $token) {
+    $key_parts = array(
+      $consumer->secret,
+      ($token) ? $token->secret : ""
+    );
+
+    $key_parts = Blogger_OAuthUtil::urlencode_rfc3986($key_parts);
+    $key = implode('&', $key_parts);
+    $request->base_string = $key;
+
+    return $key;
+  }
+}
+
+/**
+ * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in 
+ * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for 
+ * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a 
+ * verified way to the Service Provider, in a manner which is beyond the scope of this 
+ * specification.
+ *   - Chapter 9.3 ("RSA-SHA1")
+ */
+abstract class Blogger_OAuthSignatureMethod_RSA_SHA1 extends Blogger_OAuthSignatureMethod {
+  public function get_name() {
+    return "RSA-SHA1";
+  }
+
+  // Up to the SP to implement this lookup of keys. Possible ideas are:
+  // (1) do a lookup in a table of trusted certs keyed off of consumer
+  // (2) fetch via http using a url provided by the requester
+  // (3) some sort of specific discovery code based on request
+  //
+  // Either way should return a string representation of the certificate
+  protected abstract function fetch_public_cert(&$request);
+
+  // Up to the SP to implement this lookup of keys. Possible ideas are:
+  // (1) do a lookup in a table of trusted certs keyed off of consumer
+  //
+  // Either way should return a string representation of the certificate
+  protected abstract function fetch_private_cert(&$request);
+
+  public function build_signature($request, $consumer, $token) {
+    $base_string = $request->get_signature_base_string();
+    $request->base_string = $base_string;
+
+    // Fetch the private key cert based on the request
+    $cert = $this->fetch_private_cert($request);
+
+    // Pull the private key ID from the certificate
+    $privatekeyid = openssl_get_privatekey($cert);
+
+    // Sign using the key
+    $ok = openssl_sign($base_string, $signature, $privatekeyid);
+
+    // Release the key resource
+    openssl_free_key($privatekeyid);
+
+    return base64_encode($signature);
+  }
+
+  public function check_signature($request, $consumer, $token, $signature) {
+    $decoded_sig = base64_decode($signature);
+
+    $base_string = $request->get_signature_base_string();
+
+    // Fetch the public key cert based on the request
+    $cert = $this->fetch_public_cert($request);
+
+    // Pull the public key ID from the certificate
+    $publickeyid = openssl_get_publickey($cert);
+
+    // Check the computed signature against the one passed in the query
+    $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
+
+    // Release the key resource
+    openssl_free_key($publickeyid);
+
+    return $ok == 1;
+  }
+}
+
+class Blogger_OAuthRequest {
+  protected $parameters;
+  protected $http_method;
+  protected $http_url;
+  // for debug purposes
+  public $base_string;
+  public static $version = '1.0';
+  public static $POST_INPUT = 'php://input';
+
+  function __construct($http_method, $http_url, $parameters=NULL) {
+    $parameters = ($parameters) ? $parameters : array();
+    $parameters = array_merge( Blogger_OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
+    $this->parameters = $parameters;
+    $this->http_method = $http_method;
+    $this->http_url = $http_url;
+  }
+
+
+  /**
+   * attempt to build up a request from what was passed to the server
+   */
+  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
+    $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
+              ? 'http'
+              : 'https';
+    $http_url = ($http_url) ? $http_url : $scheme .
+                              '://' . $_SERVER['SERVER_NAME'] .
+                              ':' .
+                              $_SERVER['SERVER_PORT'] .
+                              $_SERVER['REQUEST_URI'];
+    $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];
+
+    // We weren't handed any parameters, so let's find the ones relevant to
+    // this request.
+    // If you run XML-RPC or similar you should use this to provide your own
+    // parsed parameter-list
+    if (!$parameters) {
+      // Find request headers
+      $request_headers = Blogger_OAuthUtil::get_headers();
+
+      // Parse the query-string to find GET parameters
+      $parameters = Blogger_OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
+
+      // It's a POST request of the proper content-type, so parse POST
+      // parameters and add those overriding any duplicates from GET
+      if ($http_method == "POST"
+          &&  isset($request_headers['Content-Type'])
+          && strstr($request_headers['Content-Type'],
+                     'application/x-www-form-urlencoded')
+          ) {
+        $post_data = Blogger_OAuthUtil::parse_parameters(
+          file_get_contents(self::$POST_INPUT)
+        );
+        $parameters = array_merge($parameters, $post_data);
+      }
+
+      // We have a Authorization-header with Blogger_OAuth data. Parse the header
+      // and add those overriding any duplicates from GET or POST
+      if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'Blogger_OAuth ') {
+        $header_parameters = Blogger_OAuthUtil::split_header(
+          $request_headers['Authorization']
+        );
+        $parameters = array_merge($parameters, $header_parameters);
+      }
+
+    }
+
+    return new Blogger_OAuthRequest($http_method, $http_url, $parameters);
+  }
+
+  /**
+   * pretty much a helper function to set up the request
+   */
+  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
+    $parameters = ($parameters) ?  $parameters : array();
+    $defaults = array("oauth_version" => Blogger_OAuthRequest::$version,
+                      "oauth_nonce" => Blogger_OAuthRequest::generate_nonce(),
+                      "oauth_timestamp" => Blogger_OAuthRequest::generate_timestamp(),
+                      "oauth_consumer_key" => $consumer->key);
+    if ($token)
+      $defaults['oauth_token'] = $token->key;
+
+    $parameters = array_merge($defaults, $parameters);
+
+    return new Blogger_OAuthRequest($http_method, $http_url, $parameters);
+  }
+
+  public function set_parameter($name, $value, $allow_duplicates = true) {
+    if ($allow_duplicates && isset($this->parameters[$name])) {
+      // We have already added parameter(s) with this name, so add to the list
+      if (is_scalar($this->parameters[$name])) {
+        // This is the first duplicate, so transform scalar (string)
+        // into an array so we can add the duplicates
+        $this->parameters[$name] = array($this->parameters[$name]);
+      }
+
+      $this->parameters[$name][] = $value;
+    } else {
+      $this->parameters[$name] = $value;
+    }
+  }
+
+  public function get_parameter($name) {
+    return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
+  }
+
+  public function get_parameters() {
+    return $this->parameters;
+  }
+
+  public function unset_parameter($name) {
+    unset($this->parameters[$name]);
+  }
+
+  /**
+   * The request parameters, sorted and concatenated into a normalized string.
+   * @return string
+   */
+  public function get_signable_parameters() {
+    // Grab all parameters
+    $params = $this->parameters;
+
+    // Remove oauth_signature if present
+    // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
+    if (isset($params['oauth_signature'])) {
+      unset($params['oauth_signature']);
+    }
+
+    return Blogger_OAuthUtil::build_http_query($params);
+  }
+
+  /**
+   * Returns the base string of this request
+   *
+   * The base string defined as the method, the url
+   * and the parameters (normalized), each urlencoded
+   * and the concated with &.
+   */
+  public function get_signature_base_string() {
+    $parts = array(
+      $this->get_normalized_http_method(),
+      $this->get_normalized_http_url(),
+      $this->get_signable_parameters()
+    );
+
+    $parts = Blogger_OAuthUtil::urlencode_rfc3986($parts);
+
+    return implode('&', $parts);
+  }
+
+  /**
+   * just uppercases the http method
+   */
+  public function get_normalized_http_method() {
+    return strtoupper($this->http_method);
+  }
+
+  /**
+   * parses the url and rebuilds it to be
+   * scheme://host/path
+   */
+  public function get_normalized_http_url() {
+    $parts = parse_url($this->http_url);
+
+    $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
+    $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
+    $host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
+    $path = (isset($parts['path'])) ? $parts['path'] : '';
+
+    if (($scheme == 'https' && $port != '443')
+        || ($scheme == 'http' && $port != '80')) {
+      $host = "$host:$port";
+    }
+    return "$scheme://$host$path";
+  }
+
+  /**
+   * builds a url usable for a GET request
+   */
+  public function to_url() {
+    $post_data = $this->to_postdata();
+    $out = $this->get_normalized_http_url();
+    if ($post_data) {
+      $out .= '?'.$post_data;
+    }
+    return $out;
+  }
+
+  /**
+   * builds the data one would send in a POST request
+   */
+  public function to_postdata() {
+    return Blogger_OAuthUtil::build_http_query($this->parameters);
+  }
+
+  /**
+   * builds the Authorization: header
+   */
+  public function to_header($realm=null) {
+    $first = true;
+	if($realm) {
+      $out = 'Authorization: Blogger_OAuth realm="' . Blogger_OAuthUtil::urlencode_rfc3986($realm) . '"';
+      $first = false;
+    } else
+      $out = 'Authorization: Blogger_OAuth';
+
+    $total = array();
+    foreach ($this->parameters as $k => $v) {
+      if (substr($k, 0, 5) != "oauth") continue;
+      if (is_array($v)) {
+        throw new Blogger_OAuthException('Arrays not supported in headers');
+      }
+      $out .= ($first) ? ' ' : ',';
+      $out .= Blogger_OAuthUtil::urlencode_rfc3986($k) .
+              '="' .
+              Blogger_OAuthUtil::urlencode_rfc3986($v) .
+              '"';
+      $first = false;
+    }
+    return $out;
+  }
+
+  public function __toString() {
+    return $this->to_url();
+  }
+
+
+  public function sign_request($signature_method, $consumer, $token) {
+    $this->set_parameter(
+      "oauth_signature_method",
+      $signature_method->get_name(),
+      false
+    );
+    $signature = $this->build_signature($signature_method, $consumer, $token);
+    $this->set_parameter("oauth_signature", $signature, false);
+  }
+
+  public function build_signature($signature_method, $consumer, $token) {
+    $signature = $signature_method->build_signature($this, $consumer, $token);
+    return $signature;
+  }
+
+  /**
+   * util function: current timestamp
+   */
+  private static function generate_timestamp() {
+    return time();
+  }
+
+  /**
+   * util function: current nonce
+   */
+  private static function generate_nonce() {
+    $mt = microtime();
+    $rand = mt_rand();
+
+    return md5($mt . $rand); // md5s look nicer than numbers
+  }
+}
+
+class Blogger_OAuthServer {
+  protected $timestamp_threshold = 300; // in seconds, five minutes
+  protected $version = '1.0';             // hi blaine
+  protected $signature_methods = array();
+
+  protected $data_store;
+
+  function __construct($data_store) {
+    $this->data_store = $data_store;
+  }
+
+  public function add_signature_method($signature_method) {
+    $this->signature_methods[$signature_method->get_name()] =
+      $signature_method;
+  }
+
+  // high level functions
+
+  /**
+   * process a request_token request
+   * returns the request token on success
+   */
+  public function fetch_request_token(&$request) {
+    $this->get_version($request);
+
+    $consumer = $this->get_consumer($request);
+
+    // no token required for the initial token request
+    $token = NULL;
+
+    $this->check_signature($request, $consumer, $token);
+
+    // Rev A change
+    $callback = $request->get_parameter('oauth_callback');
+    $new_token = $this->data_store->new_request_token($consumer, $callback);
+
+    return $new_token;
+  }
+
+  /**
+   * process an access_token request
+   * returns the access token on success
+   */
+  public function fetch_access_token(&$request) {
+    $this->get_version($request);
+
+    $consumer = $this->get_consumer($request);
+
+    // requires authorized request token
+    $token = $this->get_token($request, $consumer, "request");
+
+    $this->check_signature($request, $consumer, $token);
+
+    // Rev A change
+    $verifier = $request->get_parameter('oauth_verifier');
+    $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
+
+    return $new_token;
+  }
+
+  /**
+   * verify an api call, checks all the parameters
+   */
+  public function verify_request(&$request) {
+    $this->get_version($request);
+    $consumer = $this->get_consumer($request);
+    $token = $this->get_token($request, $consumer, "access");
+    $this->check_signature($request, $consumer, $token);
+    return array($consumer, $token);
+  }
+
+  // Internals from here
+  /**
+   * version 1
+   */
+  private function get_version(&$request) {
+    $version = $request->get_parameter("oauth_version");
+    if (!$version) {
+      // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. 
+      // Chapter 7.0 ("Accessing Protected Ressources")
+      $version = '1.0';
+    }
+    if ($version !== $this->version) {
+      throw new Blogger_OAuthException("Blogger_OAuth version '$version' not supported");
+    }
+    return $version;
+  }
+
+  /**
+   * figure out the signature with some defaults
+   */
+  private function get_signature_method($request) {
+    $signature_method = $request instanceof Blogger_OAuthRequest 
+        ? $request->get_parameter("oauth_signature_method")
+        : NULL;
+
+    if (!$signature_method) {
+      // According to chapter 7 ("Accessing Protected Ressources") the signature-method
+      // parameter is required, and we can't just fallback to PLAINTEXT
+      throw new Blogger_OAuthException('No signature method parameter. This parameter is required');
+    }
+
+    if (!in_array($signature_method,
+                  array_keys($this->signature_methods))) {
+      throw new Blogger_OAuthException(
+        "Signature method '$signature_method' not supported " .
+        "try one of the following: " .
+        implode(", ", array_keys($this->signature_methods))
+      );
+    }
+    return $this->signature_methods[$signature_method];
+  }
+
+  /**
+   * try to find the consumer for the provided request's consumer key
+   */
+  private function get_consumer($request) {
+    $consumer_key = $request instanceof Blogger_OAuthRequest 
+        ? $request->get_parameter("oauth_consumer_key")
+        : NULL;
+
+    if (!$consumer_key) {
+      throw new Blogger_OAuthException("Invalid consumer key");
+    }
+
+    $consumer = $this->data_store->lookup_consumer($consumer_key);
+    if (!$consumer) {
+      throw new Blogger_OAuthException("Invalid consumer");
+    }
+
+    return $consumer;
+  }
+
+  /**
+   * try to find the token for the provided request's token key
+   */
+  private function get_token($request, $consumer, $token_type="access") {
+    $token_field = $request instanceof Blogger_OAuthRequest
+         ? $request->get_parameter('oauth_token')
+         : NULL;
+
+    $token = $this->data_store->lookup_token(
+      $consumer, $token_type, $token_field
+    );
+    if (!$token) {
+      throw new Blogger_OAuthException("Invalid $token_type token: $token_field");
+    }
+    return $token;
+  }
+
+  /**
+   * all-in-one function to check the signature on a request
+   * should guess the signature method appropriately
+   */
+  private function check_signature($request, $consumer, $token) {
+    // this should probably be in a different method
+    $timestamp = $request instanceof Blogger_OAuthRequest
+        ? $request->get_parameter('oauth_timestamp')
+        : NULL;
+    $nonce = $request instanceof Blogger_OAuthRequest
+        ? $request->get_parameter('oauth_nonce')
+        : NULL;
+
+    $this->check_timestamp($timestamp);
+    $this->check_nonce($consumer, $token, $nonce, $timestamp);
+
+    $signature_method = $this->get_signature_method($request);
+
+    $signature = $request->get_parameter('oauth_signature');
+    $valid_sig = $signature_method->check_signature(
+      $request,
+      $consumer,
+      $token,
+      $signature
+    );
+
+    if (!$valid_sig) {
+      throw new Blogger_OAuthException("Invalid signature");
+    }
+  }
+
+  /**
+   * check that the timestamp is new enough
+   */
+  private function check_timestamp($timestamp) {
+    if( ! $timestamp )
+      throw new Blogger_OAuthException(
+        'Missing timestamp parameter. The parameter is required'
+      );
+    
+    // verify that timestamp is recentish
+    $now = time();
+    if (abs($now - $timestamp) > $this->timestamp_threshold) {
+      throw new Blogger_OAuthException(
+        "Expired timestamp, yours $timestamp, ours $now"
+      );
+    }
+  }
+
+  /**
+   * check that the nonce is not repeated
+   */
+  private function check_nonce($consumer, $token, $nonce, $timestamp) {
+    if( ! $nonce )
+      throw new Blogger_OAuthException(
+        'Missing nonce parameter. The parameter is required'
+      );
+
+    // verify that the nonce is uniqueish
+    $found = $this->data_store->lookup_nonce(
+      $consumer,
+      $token,
+      $nonce,
+      $timestamp
+    );
+    if ($found) {
+      throw new Blogger_OAuthException("Nonce already used: $nonce");
+    }
+  }
+
+}
+
+class Blogger_OAuthDataStore {
+  function lookup_consumer($consumer_key) {
+    // implement me
+  }
+
+  function lookup_token($consumer, $token_type, $token) {
+    // implement me
+  }
+
+  function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+    // implement me
+  }
+
+  function new_request_token($consumer, $callback = null) {
+    // return a new token attached to this consumer
+  }
+
+  function new_access_token($token, $consumer, $verifier = null) {
+    // return a new access token attached to this consumer
+    // for the user associated with this token if the request token
+    // is authorized
+    // should also invalidate the request token
+  }
+
+}
+
+class Blogger_OAuthUtil {
+  public static function urlencode_rfc3986($input) {
+  if (is_array($input)) {
+    return array_map(array('Blogger_OAuthUtil', 'urlencode_rfc3986'), $input);
+  } else if (is_scalar($input)) {
+    return str_replace(
+      '+',
+      ' ',
+      str_replace('%7E', '~', rawurlencode($input))
+    );
+  } else {
+    return '';
+  }
+}
+
+
+  // This decode function isn't taking into consideration the above
+  // modifications to the encoding process. However, this method doesn't
+  // seem to be used anywhere so leaving it as is.
+  public static function urldecode_rfc3986($string) {
+    return urldecode($string);
+  }
+
+  // Utility function for turning the Authorization: header into
+  // parameters, has to do some unescaping
+  // Can filter out any non-oauth parameters if needed (default behaviour)
+  // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
+  //                  see http://code.google.com/p/oauth/issues/detail?id=163
+  public static function split_header($header, $only_allow_oauth_parameters = true) {
+    $params = array();
+    if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
+      foreach ($matches[1] as $i => $h) {
+        $params[$h] = Blogger_OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
+      }
+      if (isset($params['realm'])) {
+        unset($params['realm']);
+      }
+    }
+    return $params;
+  }
+
+  // helper to try to sort out headers for people who aren't running apache
+  public static function get_headers() {
+    if (function_exists('apache_request_headers')) {
+      // we need this to get the actual Authorization: header
+      // because apache tends to tell us it doesn't exist
+      $headers = apache_request_headers();
+
+      // sanitize the output of apache_request_headers because
+      // we always want the keys to be Cased-Like-This and arh()
+      // returns the headers in the same case as they are in the
+      // request
+      $out = array();
+      foreach ($headers AS $key => $value) {
+        $key = str_replace(
+            " ",
+            "-",
+            ucwords(strtolower(str_replace("-", " ", $key)))
+          );
+        $out[$key] = $value;
+      }
+    } else {
+      // otherwise we don't have apache and are just going to have to hope
+      // that $_SERVER actually contains what we need
+      $out = array();
+      if( isset($_SERVER['CONTENT_TYPE']) )
+        $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
+      if( isset($_ENV['CONTENT_TYPE']) )
+        $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
+
+      foreach ($_SERVER as $key => $value) {
+        if (substr($key, 0, 5) == "HTTP_") {
+          // this is chaos, basically it is just there to capitalize the first
+          // letter of every word that is not an initial HTTP and strip HTTP
+          // code from przemek
+          $key = str_replace(
+            " ",
+            "-",
+            ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
+          );
+          $out[$key] = $value;
+        }
+      }
+    }
+    return $out;
+  }
+
+  // This function takes a input like a=b&a=c&d=e and returns the parsed
+  // parameters like this
+  // array('a' => array('b','c'), 'd' => 'e')
+  public static function parse_parameters( $input ) {
+    if (!isset($input) || !$input) return array();
+
+    $pairs = explode('&', $input);
+
+    $parsed_parameters = array();
+    foreach ($pairs as $pair) {
+      $split = explode('=', $pair, 2);
+      $parameter = Blogger_OAuthUtil::urldecode_rfc3986($split[0]);
+      $value = isset($split[1]) ? Blogger_OAuthUtil::urldecode_rfc3986($split[1]) : '';
+
+      if (isset($parsed_parameters[$parameter])) {
+        // We have already recieved parameter(s) with this name, so add to the list
+        // of parameters with this name
+
+        if (is_scalar($parsed_parameters[$parameter])) {
+          // This is the first duplicate, so transform scalar (string) into an array
+          // so we can add the duplicates
+          $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
+        }
+
+        $parsed_parameters[$parameter][] = $value;
+      } else {
+        $parsed_parameters[$parameter] = $value;
+      }
+    }
+    return $parsed_parameters;
+  }
+
+  public static function build_http_query($params) {
+    if (!$params) return '';
+
+    // Urlencode both keys and values
+    $keys = Blogger_OAuthUtil::urlencode_rfc3986(array_keys($params));
+    $values = Blogger_OAuthUtil::urlencode_rfc3986(array_values($params));
+    $params = array_combine($keys, $values);
+
+    // Parameters are sorted by name, using lexicographical byte value ordering.
+    // Ref: Spec: 9.1.1 (1)
+    uksort($params, 'strcmp');
+
+    $pairs = array();
+    foreach ($params as $parameter => $value) {
+      if (is_array($value)) {
+        // If two or more parameters share the same name, they are sorted by their value
+        // Ref: Spec: 9.1.1 (1)
+        // June 12th, 2010 - changed to sort because of issue 164 by hidetaka
+        sort($value, SORT_STRING);
+        foreach ($value as $duplicate_value) {
+          $pairs[] = $parameter . '=' . $duplicate_value;
+        }
+      } else {
+        $pairs[] = $parameter . '=' . $value;
+      }
+    }
+    // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
+    // Each name-value pair is separated by an '&' character (ASCII code 38)
+    return implode('&', $pairs);
+  }
+}
+
Index: readme.txt
===================================================================
--- readme.txt	(revision 545841)
+++ readme.txt	(working copy)
@@ -1,29 +1,141 @@
-=== Blogger Importer ===
-Contributors: wordpressdotorg
+=== Plugin Name ===
+Contributors: wordpressdotorg, otto42, workshopshed, SergeyBiryukov, rmccue
 Donate link: 
 Tags: importer, blogger
 Requires at least: 3.0
 Tested up to: 3.2
-Stable tag: 0.4
+Stable tag: 0.6
+License: GPLv2 or later
 
-Import posts, comments, and users from a Blogger blog.
+Imports posts, comments and categories (blogger tags) from a Blogger blog then migrates authors to Wordpress users.
 
 == Description ==
 
-This is the Blogger Importer available from the WordPress Tools->Import screen. It imports posts, comments, and users from a Blogger site into a WordPress installation.
+The Blogger Importer imports your blog data from a Blogger site into a WordPress.org installation.
 
+= Items imported =
+
+* Categories
+* Posts (published, scheduled and draft)
+* Comments (not spam)
+
+= Items not imported =
+
+* Pages
+* Images (the images will appear in your new blog but will link to the old blogspot or picassa web locations)
+
 == Installation ==
 
 1. Upload the `blogger-importer` folder to the `/wp-content/plugins/` directory
 1. Activate the plugin through the 'Plugins' menu in WordPress
-1. Go to the Tools -> Import screen, Click on Blogger
 
+= How to use =
+
+1. Blogger Importer is available from the WordPress Tools->Import screen.
+1. Press Authorise
+1. If you are not already logged into Google you will be asked to login
+1. You will be asked to grant Wordpress access to your Blogger information, to continue press Grant Access
+1. You will be presented with a list of all your blogs
+1. Select the appropriate blog and press the import button
+1. Wait whilst the posts and comments are imported
+1. Press the Set Authors button
+1. Select the appropriate mapping for the authors
+1. Review categories, posts and comments
+
+You can now remove the importer plugin if you no longer need to use it.
+
 == Frequently Asked Questions ==
 
+= How do I re-import? =
+
+Press the clear account information button, then re-connect to blogger and re-import, the importer is designed not to re-import the same posts. If you need to do a full re-import then delete the posts and then empty the trash before re-importing.
+
+= How do I know which posts were imported? = 
+
+Each of the posts loaded is tagged with a meta tags indicating where the posts were loaded from. The permalink will be set to the visible URL if the post was published or the internal ID if it was still a draft or scheduled post
+
+* blogger_author
+* blogger_blog
+* blogger_permalink
+
+= Why does it keep stopping? = 
+
+The importer is designed not to overload blogger or your site so only imports in batches and will run for a fixed number of seconds before pausing, the admin screen will refresh every few seconds to show how many it has done so far. Press continue to continue importing posts and comments.
+
+= After importing there are a lot of categories =
+
+Blogger does not distinguish between tags and categories so you will likely want to review what was imported and then use the categories to tags converter
+
+= What about pages? =
+
+This importer does not handle blogger pages, you will need to manually transfer them.
+
+= What about images? =
+
+The importer will simply load the tags for the images as they appear in your source data, so you will have references to blogspot and picassa based images. If you do not migrate these with a separate tool then these will be lost when you delete your old blogger blog.
+
+= Are the permalinks the same? =
+
+No, Wordpress and Blogger handle the permalinks differently. However, it is possible to use the redirection plugin to map the old URLs across to the new URLs.
+
+= What about future posts? =
+
+The scheduled posts will be transferred and will be published as specified. However, Blogger and Wordpress handle drafts differently, Wordpress does not support dates on draft posts so you will need to use a plugin if you wish to plan your writing schedule.
+
+= My posts and comments moved across but some things are stripped out =
+
+The importer uses the SimplePie classes to process the data, these in turn use a Simplepie_Sanitize class to remove potentially malicious code from the source data.
+
 == Screenshots ==
 
+== Reference ==
+
+* https://developers.google.com/blogger/docs/1.0/developers_guide_php
+* https://developers.google.com/gdata/articles/oauth
+
+== Other changes to review ==
+
+Review the santisation processes, are these being done twice?
+
+http://core.trac.wordpress.org/attachment/ticket/15737/blogger-importer.patch
+http://core.trac.wordpress.org/ticket/15737
+
+
+
+http://core.trac.wordpress.org/ticket/7652
+http://core.trac.wordpress.org/attachment/ticket/7652/7652-blogger.diff
+http://core.trac.wordpress.org/attachment/ticket/7652/7652-separate.diff
+
+Move the Javascript into a separate file, pass the parameters and localised strings using wp_localize_script
+
+Inconsistent UI (sometimes you get a set of tools graphic, sometimes not)
+
+Handle geotags See http://codex.wordpress.org/Geodata
+
+
+
 == Changelog ==
 
+= 0.6 =
+* Merged in fix by SergeyBiryukov http://core.trac.wordpress.org/ticket/16012
+* Merged in rmccue change to get_total_results to also use SimplePie from http://core.trac.wordpress.org/attachment/ticket/7652/7652-blogger.diff
+* Reviewed in rmccue's changes in http://core.trac.wordpress.org/attachment/ticket/7652/7652-separate.diff issues with date handling functions so skipped those
+* Moved SimplePie functions in  new class WP_SimplePie_Blog_Item incorporating get_draft_status and get_updated and convert date
+* Andy from Workshopshed tested comments from source blog GMT-8, destination London (currently GMT-1), comment dates transferred correctly.
+* Andy from Workshopshed Fixed typo in oauth_get
+
+= 0.5 =
+* Change by Otto42, rmccue to use Simplepie XML processing rather than Atomparser, http://core.trac.wordpress.org/ticket/14525 ref: http://core.trac.wordpress.org/attachment/ticket/7652/7652-blogger.diff
+  this also fixes http://core.trac.wordpress.org/ticket/15560 
+* Change by Otto42 to use OAuth rather than AuthSub authentication, should make authentication more reliable
+* Fix by Andy from Workshopshed to load comments correctly
+* Fix by Andy from Workshopshed to correctly pass the blogger start-index and max-results parameters to oAuth functions and to process more than one batch http://core.trac.wordpress.org/ticket/19096
+* Fix by Andy from Workshopshed error about incorrect enqueuing of scripts also changed styles to work the same
+* Change by Andy from Workshopshed testing in debug mode and wrapped ajax return into a function to suppress debug messages
+* Fix by Andy from Workshopshed notices for undefined variables.
+* Change by Andy from Workshopshed Added tooltip to results table to show numbers of posts and comments skipped (duplicates / missing key)
+* Fix by Andy from Workshopshed incorrectly checking for duplicates based on only the date and username, this gave false positives when large numbers of comments, particularly anonymous ones.
+
 = 0.4 =
 * Fix for tracking images being added by Blogger to non-authenticated feeds http://core.trac.wordpress.org/ticket/17623
 
@@ -35,9 +147,11 @@
 
 == Upgrade Notice ==
 
-= 0.4 =
-* Fix for tracking images being added by Blogger to non-authenticated feeds http://core.trac.wordpress.org/ticket/17623
+= 0.6 =
 
-= 0.3 =
-* Bugfix for 403 Invalid AuthSub Token
+Merged in fixes found in Trac
 
+= 0.5 =
+
+This version is a significant re-write based on previous versions. 
+
