WordPress.org

Make WordPress Core

Ticket #14525: 14525.combined.patch

File 14525.combined.patch, 96.9 KB (added by SergeyBiryukov, 2 years ago)
  • blogger-importer-blogitem.php

     
     1<?php 
     2 
     3/** 
     4* Based on WP_SimplePieAtomPub_Item 
     5* Expect this to become part of core wordpress at some point. 
     6* See http://core.trac.wordpress.org/ticket/7652 
     7*  
     8* Todo GeoTag parsing 
     9* http://codex.wordpress.org/Geodata 
     10*  
     11 */ 
     12 
     13define('SIMPLEPIE_NAMESPACE_ATOMPUB', 'http://www.w3.org/2007/app');  
     14          
     15        /**  
     16         * SimplePie Helper for AtomPub  
     17         *  
     18         * @package WordPress  
     19         * @subpackage Publishing  
     20         * @since 3.1  
     21         */  
     22if ( !class_exists( 'WP_SimplePie_Blog_Item' ) ) {     
     23        class WP_SimplePie_Blog_Item extends SimplePie_Item {  
     24                /**  
     25                 * Constructor  
     26                 */  
     27                function WP_SimplePieAtomPub_Item($feed, $data) {  
     28                        parent::SimplePie_Item($feed, $data);  
     29                }  
     30          
     31                /**  
     32                 * Get the status of the entry  
     33                 *  
     34                 * @return bool True if the item is a draft, false otherwise  
     35                 */  
     36                function get_draft_status() {  
     37                        $draft = false;  
     38                        if (($control = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOMPUB, 'control')) && !empty($control[0]['child'][SIMPLEPIE_NAMESPACE_ATOMPUB]['draft'][0]['data'])) {  
     39                                $draft = ('yes' == $control[0]['child'][SIMPLEPIE_NAMESPACE_ATOMPUB]['draft'][0]['data']);  
     40                        }  
     41                        return $draft;  
     42                }  
     43  
     44            //Tried using date functions from http://core.trac.wordpress.org/attachment/ticket/7652/7652-separate.diff 
     45            //but ended up with 1970s dates so returned to Otto's version which is much simplified 
     46                function get_updated() {  
     47                $temparray = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'); 
     48                if ( isset( $temparray[0]['data'] ) ) return $this->convert_date($temparray[0]['data']); 
     49                else return NULL; 
     50            } 
     51          
     52            function get_published() {  
     53                $temparray = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'); 
     54                if ( isset( $temparray[0]['data'] ) ) return $this->convert_date($temparray[0]['data']); 
     55                else return NULL; 
     56            } 
     57             
     58            function convert_date( $date ) { 
     59                    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); 
     60                    $offset = iso8601_timezone_to_offset( $date_bits[7] ); 
     61                        $timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]); 
     62                        $timestamp -= $offset; // Convert from Blogger local time to GMT 
     63                        $timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time 
     64                        return gmdate('Y-m-d H:i:s', $timestamp); 
     65                }     
     66      
     67            //Don't Sanitize the ID, the default get_id was cleaning our IDs and that meant that nested comments did not work       
     68                function get_id() 
     69                { 
     70                                if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) 
     71                                { 
     72                                        return $return[0]['data']; 
     73                                } 
     74             } 
     75        
     76    } 
     77 
     78} 
     79 
     80?> 
     81 No newline at end of file 
  • blogger-importer-sanitize.php

     
     1<?php 
     2/** 
     3 * New class to sanitize trusted content from blogger import 
     4 * Based on the SimplePie_Sanitize class by Ryan Parman and Geoffrey Sneddon 
     5 * 
     6 */ 
     7 
     8class Blogger_Importer_Sanitize extends Simplepie_Sanitize 
     9{ 
     10    // Private vars 
     11    var $base; 
     12 
     13    // Options 
     14    var $image_handler = ''; 
     15    var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'font', 'form', 
     16        'frame', 'frameset', 'html', 'input', 'marquee', 'meta', 'script', 'style'); 
     17    //Allow iframe (new style) and embed, param and object(old style) so that we get youtube videos transferred 
     18    //Allow object and noscript for Amazon widgets 
     19    var $encode_instead_of_strip = false; 
     20    var $strip_attributes = array('bgsound','class', 'expr', 'id', 'imageanchor', 'onclick', 'onerror', 
     21        'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); 
     22    //Allow styles so we don't have to redo in Wordpress 
     23    //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 
     24    var $output_encoding = 'UTF-8'; 
     25    var $enable_cache = true; 
     26    var $cache_location = './cache'; 
     27    var $cache_name_function = 'md5'; 
     28    var $cache_class = 'SimplePie_Cache'; 
     29    var $file_class = 'SimplePie_File'; 
     30    var $timeout = 10; 
     31    var $useragent = ''; 
     32    var $force_fsockopen = false; 
     33 
     34    var $replace_url_attributes = array('a' => 'href', 'area' => 'href', 
     35        'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 
     36        'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite'); 
     37 
     38        function _normalize_tag( $matches ) { 
     39                return '<' . strtolower( $matches[1] ); 
     40        } 
     41 
     42    function sanitize($data, $type, $base = '') 
     43    { 
     44        //Simplified function 
     45        $data = trim($data); 
     46 
     47        // Remappings 
     48                $data = str_replace('<br>', '<br />', $data); 
     49                $data = str_replace('<hr>', '<hr />', $data); 
     50        //<span style="font-weight:bold;">Workshopshed:</span> > <b>Workshopshed:</b> 
     51        $data =  preg_replace('|(<span style="font-weight:bold;">)(?<!<span style="font-weight:bold;">).*(.*)(</span>)|', '<strong>$2</strong>', $data); 
     52         
     53        //N.B. Don't strip comments as blogger uses <!--more--> which is the same as Wordpress 
     54 
     55        //Now clean up 
     56        foreach ($this->strip_htmltags as $tag) { 
     57            $pcre = "/<($tag)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$tag" . 
     58                SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>|(\/)?>)/siU'; 
     59            while (preg_match($pcre, $data)) { 
     60                $data = preg_replace_callback($pcre, array(&$this, 'do_strip_htmltags'), $data); 
     61            } 
     62        } 
     63 
     64        foreach ($this->strip_attributes as $attrib) { 
     65            $data = preg_replace('/(<[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*)' . 
     66                SIMPLEPIE_PCRE_HTML_ATTRIBUTE . trim($attrib) . '(?:\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?' . 
     67                SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>/', '\1\2\3>', $data); 
     68        } 
     69 
     70        // Replace relative URLs 
     71        $this->base = $base; 
     72        foreach ($this->replace_url_attributes as $element => $attributes) { 
     73            $data = $this->replace_urls($data, $element, $attributes); 
     74        } 
     75 
     76        // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags. 
     77        if (isset($this->image_handler) && ((string )$this->image_handler) !== '' && $this-> 
     78            enable_cache) { 
     79            $images = SimplePie_Misc::get_element('img', $data); 
     80            foreach ($images as $img) { 
     81                if (isset($img['attribs']['src']['data'])) { 
     82                    $image_url = call_user_func($this->cache_name_function, $img['attribs']['src']['data']); 
     83                    $cache = call_user_func(array($this->cache_class, 'create'), $this-> 
     84                        cache_location, $image_url, 'spi'); 
     85                    if ($cache->load()) { 
     86                        $img['attribs']['src']['data'] = $this->image_handler . $image_url; 
     87                        $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data); 
     88                    } else { 
     89                        $file = &new $this->file_class($img['attribs']['src']['data'], $this->timeout, 5, 
     90                            array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this-> 
     91                            force_fsockopen); 
     92                        $headers = $file->headers; 
     93                        if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file-> 
     94                            status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) { 
     95                            if ($cache->save(array('headers' => $file->headers, 'body' => $file->body))) { 
     96                                $img['attribs']['src']['data'] = $this->image_handler . $image_url; 
     97                                $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data); 
     98                            } else { 
     99                                trigger_error("$this->cache_location is not writeable", E_USER_WARNING); 
     100                            } 
     101                        } 
     102                    } 
     103                } 
     104            } 
     105        } 
     106 
     107        // Having (possibly) taken stuff out, there may now be whitespace at the beginning/end of the data 
     108        $data = trim($data); 
     109         
     110        // Normalise tags 
     111        $data = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $data); 
     112 
     113        return $data; 
     114    } 
     115 
     116    function replace_urls($data, $tag, $attributes) 
     117    { 
     118        if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) { 
     119            $elements = SimplePie_Misc::get_element($tag, $data); 
     120            foreach ($elements as $element) { 
     121                if (is_array($attributes)) { 
     122                    foreach ($attributes as $attribute) { 
     123                        if (isset($element['attribs'][$attribute]['data'])) { 
     124                            $element['attribs'][$attribute]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attribute]['data'], 
     125                                $this->base); 
     126                            $new_element = SimplePie_Misc::element_implode($element); 
     127                            $data = str_replace($element['full'], $new_element, $data); 
     128                            $element['full'] = $new_element; 
     129                        } 
     130                    } 
     131                } elseif (isset($element['attribs'][$attributes]['data'])) { 
     132                    $element['attribs'][$attributes]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attributes]['data'], 
     133                        $this->base); 
     134                    $data = str_replace($element['full'], SimplePie_Misc::element_implode($element), 
     135                        $data); 
     136                } 
     137            } 
     138        } 
     139        return $data; 
     140    } 
     141 
     142 
     143} 
     144?> 
  • blogger-importer.css

     
     1 
     2td { text-align: center; line-height: 2em;} 
     3thead td { font-weight: bold; } 
     4.bar { 
     5        width: 200px; 
     6        text-align: left; 
     7        line-height: 2em; 
     8        padding: 0px; 
     9} 
     10.ind { 
     11        position: absolute; 
     12        background-color: #83B4D8; 
     13        width: 1px; 
     14        z-index: 9; 
     15} 
     16.stat { 
     17        z-index: 10; 
     18        position: relative; 
     19        text-align: center; 
     20} 
     21td.submit { 
     22        margin:0; 
     23        padding:0; 
     24} 
     25 
     26td { 
     27        padding-left:10px; 
     28        padding-right:10px; 
     29} 
  • blogger-importer.php

     
    22/* 
    33Plugin Name: Blogger Importer 
    44Plugin URI: http://wordpress.org/extend/plugins/blogger-importer/ 
    5 Description: Import posts, comments, tags, and attachments from a Blogger blog. 
     5Description: Imports posts, comments and tags from a Blogger blog then migrates authors to Wordpress users. 
    66Author: wordpressdotorg 
    77Author URI: http://wordpress.org/ 
    8 Version: 0.4 
    9 Stable tag: 0.4 
    10 License: GPL version 2 or later - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 
     8Version: 0.6 
     9License: GPL v2 - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 
    1110*/ 
    1211 
    1312if ( !defined('WP_LOAD_IMPORTERS') ) 
     
    1615// Load Importer API 
    1716require_once ABSPATH . 'wp-admin/includes/import.php'; 
    1817 
     18// Load Simple Pie 
     19require_once ABSPATH . WPINC . '/class-feed.php'; 
     20require_once 'blogger-importer-sanitize.php'; 
     21require_once 'blogger-importer-blogitem.php'; 
     22 
     23// Load OAuth library 
     24require_once 'oauth.php'; 
     25 
    1926if ( !class_exists( 'WP_Importer' ) ) { 
    2027        $class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php'; 
    2128        if ( file_exists( $class_wp_importer ) ) 
     
    3037 * @var int 
    3138 * @since unknown 
    3239 */ 
    33 define( 'MAX_RESULTS',        50 ); 
     40define( 'MAX_RESULTS', 25 ); 
    3441 
    3542/** 
    3643 * How many seconds to let the script run 
     
    6168if ( class_exists( 'WP_Importer' ) ) { 
    6269class Blogger_Import extends WP_Importer { 
    6370 
     71        function Blogger_Import() { 
     72                global $importer_started; 
     73                $importer_started = time(); 
     74                if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) { 
     75                        add_action('admin_print_scripts', array(&$this, 'queue_scripts')); 
     76            add_action('admin_print_styles', array(&$this, 'queue_style')); 
     77                } 
     78        } 
     79     
     80    function queue_scripts($hook) { 
     81        wp_enqueue_script('jquery'); 
     82    } 
     83     
     84    function queue_style() { 
     85        wp_enqueue_style('BloggerImporter',plugins_url('/blogger-importer.css', __FILE__)); 
     86    } 
     87 
    6488        // Shows the welcome screen and the magic auth link. 
    6589        function greet() { 
    6690                $next_url = get_option('siteurl') . '/wp-admin/index.php?import=blogger&amp;noheader=true'; 
    67                 $auth_url = "https://www.google.com/accounts/AuthSubRequest"; 
     91                $auth_url = $this->get_oauth_link(); 
    6892                $title = __('Import Blogger', 'blogger-importer'); 
    6993                $welcome = __('Howdy! This importer allows you to import posts and comments from your Blogger account into your WordPress site.', 'blogger-importer'); 
    7094                $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'); 
     
    76100                ".screen_icon()." 
    77101                <h2>$title</h2> 
    78102                <p>$welcome</p><p>$prereqs</p><p>$stepone</p> 
    79                         <form action='$auth_url' method='get'> 
     103                        <form action='{$auth_url['url']}' method='get'> 
    80104                                <p class='submit' style='text-align:left;'> 
    81105                                        <input type='submit' class='button' value='$auth' /> 
    82                                         <input type='hidden' name='scope' value='http://www.blogger.com/feeds/' /> 
    83                                         <input type='hidden' name='session' value='1' /> 
    84                                         <input type='hidden' name='secure' value='0' /> 
    85                                         <input type='hidden' name='next' value='$next_url' /> 
     106                                        <input type='hidden' name='oauth_token' value='{$auth_url['oauth_token']}' /> 
     107                                        <input type='hidden' name='oauth_callback' value='{$auth_url['oauth_callback']}' /> 
    86108                                </p> 
    87109                        </form> 
    88110                </div>\n"; 
    89111        } 
     112         
     113        function get_oauth_link() { 
     114                // Establish an Blogger_OAuth consumer 
     115                $base_url = get_option('siteurl') . '/wp-admin'; 
     116                $request_token_endpoint = 'https://www.google.com/accounts/OAuthGetRequestToken'; 
     117                $authorize_endpoint = 'https://www.google.com/accounts/OAuthAuthorizeToken'; 
    90118 
     119                $test_consumer = new Blogger_OAuthConsumer('anonymous', 'anonymous', NULL); // anonymous is a google thing to allow non-registered apps to work 
     120 
     121                //prepare to get request token 
     122                $sig_method = new Blogger_OAuthSignatureMethod_HMAC_SHA1(); 
     123                $parsed = parse_url($request_token_endpoint); 
     124                $params = array('callback' => $base_url, 'scope'=>'http://www.blogger.com/feeds/', 'xoauth_displayname'=>'WordPress'); 
     125 
     126                $req_req = Blogger_OAuthRequest::from_consumer_and_token($test_consumer, NULL, "GET", $request_token_endpoint, $params); 
     127                $req_req->sign_request($sig_method, $test_consumer, NULL); 
     128 
     129                // go get the request tokens from Google 
     130                $req_token = wp_remote_retrieve_body(wp_remote_get($req_req->to_url(), array('sslverify'=>false) ) ); 
     131 
     132                // parse the tokens 
     133                parse_str ($req_token,$tokens); 
     134 
     135                $oauth_token = $tokens['oauth_token']; 
     136                $oauth_token_secret = $tokens['oauth_token_secret']; 
     137 
     138                $callback_url = "$base_url/index.php?import=blogger&noheader=true&token=$oauth_token&token_secret=$oauth_token_secret"; 
     139 
     140                return array('url'=>$authorize_endpoint, 'oauth_token'=>$oauth_token, 'oauth_callback'=>$callback_url );         
     141        } 
     142 
    91143        function uh_oh($title, $message, $info) { 
    92144                echo "<div class='wrap'>"; 
    93145                screen_icon(); 
     
    95147        } 
    96148 
    97149        function auth() { 
    98                 // We have a single-use token that must be upgraded to a session token. 
    99                 $token = urldecode( preg_replace( '/[^%-_0-9a-zA-Z]/', '', $_GET['token'] ) ); 
    100                 $headers = array( 
    101                         "GET /accounts/AuthSubSessionToken HTTP/1.0", 
    102                         "Authorization: AuthSub token=\"$token\"" 
    103                 ); 
    104                 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
    105                 $sock = $this->_get_auth_sock( ); 
    106                 if ( ! $sock ) return false; 
    107                 $response = $this->_txrx( $sock, $request ); 
    108                 preg_match( '/token=([%-_0-9a-z]+)/i', $response, $matches ); 
    109                 if ( empty( $matches[1] ) ) { 
    110                         $this->uh_oh( 
    111                                 __( 'Authorization failed' , 'blogger-importer'), 
    112                                 __( 'Something went wrong. If the problem persists, send this info to support:' , 'blogger-importer'), 
    113                                 htmlspecialchars($response) 
    114                         ); 
    115                         return false; 
    116                 } 
    117                 $this->token = urldecode( $matches[1] ); 
    118  
     150                // we have a authorized request token now, so upgrade it to an access token              
     151                $token = $_GET['token']; 
     152                $token_secret = $_GET['token_secret']; 
     153                 
     154                $oauth_access_token_endpoint  = 'https://www.google.com/accounts/OAuthGetAccessToken'; 
     155                 
     156                // auth the token 
     157                $test_consumer = new Blogger_OAuthConsumer('anonymous', 'anonymous', NULL); 
     158                $auth_token = new Blogger_OAuthConsumer($token, $token_secret); 
     159                $access_token_req = new Blogger_OAuthRequest("GET", $oauth_access_token_endpoint); 
     160                $access_token_req = $access_token_req->from_consumer_and_token($test_consumer, $auth_token, "GET", $oauth_access_token_endpoint); 
     161                 
     162                $access_token_req->sign_request(new Blogger_OAuthSignatureMethod_HMAC_SHA1(),$test_consumer, $auth_token); 
     163                 
     164                $after_access_request = wp_remote_retrieve_body(wp_remote_get($access_token_req->to_url(), array('sslverify'=>false) ) ); 
     165                 
     166                parse_str($after_access_request,$access_tokens); 
     167                 
     168                $this->token = $access_tokens['oauth_token']; 
     169                $this->token_secret = $access_tokens['oauth_token_secret']; 
     170         
    119171                wp_redirect( remove_query_arg( array( 'token', 'noheader' ) ) ); 
    120172        } 
     173         
     174        // get a URL using the oauth token for authentication (returns false on failure) 
     175        function oauth_get($url, $params=NULL) { 
     176                $test_consumer = new Blogger_OAuthConsumer('anonymous', 'anonymous', NULL); 
     177                $goog = new Blogger_OAuthConsumer($this->token, $this->token_secret, NULL); 
     178                $request = new Blogger_OAuthRequest("GET", $url, $params); 
    121179 
    122         function get_token_info() { 
    123                 $headers = array( 
    124                         "GET /accounts/AuthSubTokenInfo  HTTP/1.0", 
    125                         "Authorization: AuthSub token=\"$this->token\"" 
    126                 ); 
    127                 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
    128                 $sock = $this->_get_auth_sock( ); 
    129                 if ( ! $sock ) return; 
    130                 $response = $this->_txrx( $sock, $request ); 
    131                 return $this->parse_response($response); 
    132         } 
     180        //Ref: Not importing properly http://core.trac.wordpress.org/ticket/19096   
     181                $blog_req = $request->from_consumer_and_token($test_consumer, $goog, 'GET', $url,$params); 
    133182 
    134         function token_is_valid() { 
    135                 $info = $this->get_token_info(); 
     183                $blog_req->sign_request(new Blogger_OAuthSignatureMethod_HMAC_SHA1(),$test_consumer,$goog); 
    136184 
    137                 if ( $info['code'] == 200 ) 
    138                         return true; 
    139  
    140                 return false; 
     185                $data = wp_remote_get($blog_req->to_url(), array('sslverify'=>false) ); 
     186                 
     187                if ( wp_remote_retrieve_response_code( $data ) == 200 ) { 
     188                        $response = wp_remote_retrieve_body( $data ); 
     189                } else { 
     190                        $response = false; 
     191                } 
     192                 
     193                return $response; 
    141194        } 
    142  
     195         
    143196        function show_blogs($iter = 0) { 
    144197                if ( empty($this->blogs) ) { 
    145                         $headers = array( 
    146                                 "GET /feeds/default/blogs HTTP/1.0", 
    147                                 "Host: www.blogger.com", 
    148                                 "Authorization: AuthSub token=\"$this->token\"" 
    149                         ); 
    150                         $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
    151                         $sock = $this->_get_blogger_sock( ); 
    152                         if ( ! $sock ) return; 
    153                         $response = $this->_txrx( $sock, $request ); 
     198                        $xml = $this->oauth_get('https://www.blogger.com/feeds/default/blogs'); 
    154199 
    155                         // Quick and dirty XML mining. 
    156                         list( $headers, $xml ) = explode( "\r\n\r\n", $response ); 
    157                         $p = xml_parser_create(); 
    158                         xml_parse_into_struct($p, $xml, $vals, $index); 
    159                         xml_parser_free($p); 
    160  
    161                         $this->title = $vals[$index['TITLE'][0]]['value']; 
    162  
    163200                        // Give it a few retries... this step often flakes out the first time. 
    164                         if ( empty( $index['ENTRY'] ) ) { 
     201                        if ( empty( $xml ) ) { 
    165202                                if ( $iter < 3 ) { 
    166203                                        return $this->show_blogs($iter + 1); 
    167204                                } else { 
     
    173210                                        return false; 
    174211                                } 
    175212                        } 
    176  
    177                         foreach ( $index['ENTRY'] as $i ) { 
    178                                 $blog = array(); 
    179                                 while ( ( $tag = $vals[$i] ) && ! ( $tag['tag'] == 'ENTRY' && $tag['type'] == 'close' ) ) { 
    180                                         if ( $tag['tag'] == 'TITLE' ) { 
    181                                                 $blog['title'] = $tag['value']; 
    182                                         } elseif ( $tag['tag'] == 'SUMMARY' ) { 
    183                                                 $blog['summary'] = $tag['value']; 
    184                                         } elseif ( $tag['tag'] == 'LINK' ) { 
    185                                                 if ( $tag['attributes']['REL'] == 'alternate' && $tag['attributes']['TYPE'] == 'text/html' ) { 
    186                                                         $parts = parse_url( $tag['attributes']['HREF'] ); 
    187                                                         $blog['host'] = $parts['host']; 
    188                                                 } elseif ( $tag['attributes']['REL'] == 'edit' ) { 
    189                                                         $blog['gateway'] = $tag['attributes']['HREF']; 
    190                                                 } elseif ( $tag['attributes']['REL'] == 'http://schemas.google.com/g/2005#post' ) { 
    191                                                         $parts = parse_url( $tag['attributes']['HREF'] ); 
    192                                                         $blog['posts_host'] = $parts['host']; 
    193                                                         $blog['posts_path'] = $parts['path']; 
    194                                                 } 
    195                                         } 
    196                                         ++$i; 
    197                                 } 
     213                         
     214                        $feed = new SimplePie(); 
     215                        $feed->set_raw_data($xml); 
     216                        $feed->init(); 
     217                         
     218                        foreach ($feed->get_items() as $item) { 
     219                $blog = array(); //reset 
     220                                $blog['title'] = $item->get_title(); 
     221                                $blog['summary'] = $item->get_description(); 
     222                 
     223                //ID is of the form tag:blogger.com,1999:blog-417730729915399755 
     224                //We need that number from the end 
     225                $rawid = explode('-',$item->get_id()); 
     226                $blog['id'] = $rawid[count($rawid)-1]; 
     227                         
     228                                $parts = parse_url( $item->get_link( 0, 'alternate' ) ); 
     229                                $blog['host'] = $parts['host']; 
     230                                $blog['gateway'] = $item->get_link( 0, 'edit' ); 
     231                                $blog['posts_url'] = $item->get_link( 0, 'http://schemas.google.com/g/2005#post' ); 
     232                 
     233                //AGC:20/4/2012 Developers guide suggests that the correct feed is located as follows 
     234                //See https://developers.google.com/blogger/docs/1.0/developers_guide_php 
     235                $blog['comments_url'] =  "http://www.blogger.com/feeds/{$blog['id']}/comments/default"; 
     236                                 
    198237                                if ( ! empty ( $blog ) ) { 
    199                                         $blog['total_posts'] = $this->get_total_results('posts', $blog['host']); 
    200                                         $blog['total_comments'] = $this->get_total_results('comments', $blog['host']); 
     238                                        $blog['total_posts'] = $this->get_total_results( $blog['posts_url'] ); 
     239                    $blog['total_comments'] = $this->get_total_results($blog['comments_url']); 
     240                     
    201241                                        $blog['mode'] = 'init'; 
    202242                                        $this->blogs[] = $blog; 
    203243                                } 
     244 
    204245                        } 
    205  
     246                         
    206247                        if ( empty( $this->blogs ) ) { 
    207248                                $this->uh_oh( 
    208249                                        __('No blogs found', 'blogger-importer'), 
     
    210251                                        '' 
    211252                                ); 
    212253                                return false; 
    213                         } 
     254                        }                        
    214255                } 
     256         
     257//Should probably be using WP_LIST_TABLE here rather than manually rendering a table in html 
     258//http://wpengineer.com/2426/wp_list_table-a-step-by-step-guide/         
    215259//echo '<pre>'.print_r($this,1).'</pre>'; 
    216260                $start    = esc_js( __('Import', 'blogger-importer') ); 
    217261                $continue = esc_js( __('Continue', 'blogger-importer') ); 
     
    230274                $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'); 
    231275 
    232276                $interval = STATUS_INTERVAL * 1000; 
     277        $init = ''; 
     278        $rows = ''; 
    233279 
    234280                foreach ( $this->blogs as $i => $blog ) { 
    235281                        if ( $blog['mode'] == 'init' ) 
     
    242288                        $blogtitle = esc_js( $blog['title'] ); 
    243289                        $pdone = isset($blog['posts_done']) ? (int) $blog['posts_done'] : 0; 
    244290                        $cdone = isset($blog['comments_done']) ? (int) $blog['comments_done'] : 0; 
    245                         $init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}'," . $this->get_js_status($i) . ');'; 
     291                        $init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}','" . $this->get_js_status($i) . '\');'; 
    246292                        $pstat = "<div class='ind' id='pind$i'>&nbsp;</div><div id='pstat$i' class='stat'>$pdone/{$blog['total_posts']}</div>"; 
    247293                        $cstat = "<div class='ind' id='cind$i'>&nbsp;</div><div id='cstat$i' class='stat'>$cdone/{$blog['total_comments']}</div>"; 
    248294                        $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"; 
     
    258304                                this.blog   = i; 
    259305                                this.mode   = mode; 
    260306                                this.title  = title; 
    261                                 this.status = status; 
     307                eval('this.status='+status); 
    262308                                this.button = document.getElementById('submit'+this.blog); 
    263309                        }; 
    264310                        blog.prototype = { 
     
    311357                                }, 
    312358                                update: function() { 
    313359                                        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'); 
     360                    jQuery('#pstat'+this.blog).attr('title', 'Posts skipped '+this.status.p3); 
    314361                                        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'); 
     362                    jQuery('#cstat'+this.blog).attr('title', 'Comments skipped '+this.status.c3); 
     363 
    315364                                }, 
    316365                                stop: function() { 
    317366                                        this.cont = false; 
     
    372421        function have_time() { 
    373422                global $importer_started; 
    374423                if ( time() - $importer_started > MAX_EXECUTION_TIME ) 
    375                         die('continue'); 
     424                        self::ajax_die('continue'); 
    376425                return true; 
    377426        } 
    378  
    379         function get_total_results($type, $host) { 
    380                 $headers = array( 
    381                         "GET /feeds/$type/default?max-results=1&start-index=2 HTTP/1.0", 
    382                         "Host: $host", 
    383                         "Authorization: AuthSub token=\"$this->token\"" 
    384                 ); 
    385                 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
    386                 $sock = $this->_get_blogger_sock( $host ); 
    387                 if ( ! $sock ) return; 
    388                 $response = $this->_txrx( $sock, $request ); 
    389                 $response = $this->parse_response( $response ); 
    390                 $parser = xml_parser_create(); 
    391                 xml_parse_into_struct($parser, $response['body'], $struct, $index); 
    392                 xml_parser_free($parser); 
    393                 $total_results = $struct[$index['OPENSEARCH:TOTALRESULTS'][0]]['value']; 
    394                 return (int) $total_results; 
     427         
     428        function get_total_results($url) {               
     429                $response = $this->oauth_get( $url, array('max-results'=>1, 'start-index'=>2) ); 
     430         
     431        $feed = new SimplePie();  
     432        $feed->set_raw_data($response);  
     433        $feed->init();  
     434        $results = $feed->get_channel_tags('http://a9.com/-/spec/opensearchrss/1.0/', 'totalResults');  
     435        
     436        $total_results = $results[0]['data'];  
     437        unset($feed);  
     438        return (int) $total_results;  
    395439        } 
    396440 
    397441        function import_blog($blogID) { 
    398                 global $importing_blog; 
     442                global $importing_blog; 
    399443                $importing_blog = $blogID; 
    400444 
    401445                if ( isset($_GET['authors']) ) 
    402446                        return print($this->get_author_form()); 
    403447 
    404                 header('Content-Type: text/plain'); 
    405  
    406448                if ( isset($_GET['status']) ) 
    407                         die($this->get_js_status()); 
     449                        self::ajax_die($this->get_js_status()); 
    408450 
    409451                if ( isset($_GET['saveauthors']) ) 
    410                         die($this->save_authors()); 
     452                        self::ajax_die($this->save_authors()); 
    411453 
    412                 $blog = $this->blogs[$blogID]; 
    413                 $total_results = $this->get_total_results('posts', $blog['host']); 
    414                 $this->blogs[$importing_blog]['total_posts'] = $total_results; 
    415  
    416                 $start_index = $total_results - MAX_RESULTS + 1; 
    417  
     454        //Simpler counting for posts as we load them forwards 
    418455                if ( isset( $this->blogs[$importing_blog]['posts_start_index'] ) ) 
    419456                        $start_index = (int) $this->blogs[$importing_blog]['posts_start_index']; 
    420                 elseif ( $total_results > MAX_RESULTS ) 
    421                         $start_index = $total_results - MAX_RESULTS + 1; 
    422457                else 
    423458                        $start_index = 1; 
    424459 
     
    426461                if ( $start_index > 0 ) { 
    427462                        // Grab all the posts 
    428463                        $this->blogs[$importing_blog]['mode'] = 'posts'; 
    429                         $query = "start-index=$start_index&max-results=" . MAX_RESULTS; 
    430464                        do { 
     465                          
    431466                                $index = $struct = $entries = array(); 
    432                                 $headers = array( 
    433                                         "GET {$blog['posts_path']}?$query HTTP/1.0", 
    434                                         "Host: {$blog['posts_host']}", 
    435                                         "Authorization: AuthSub token=\"$this->token\"" 
    436                                 ); 
    437                                 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
    438                                 $sock = $this->_get_blogger_sock( $blog['posts_host'] ); 
    439                                 if ( ! $sock ) return; // TODO: Error handling 
    440                                 $response = $this->_txrx( $sock, $request ); 
    441467 
    442                                 $response = $this->parse_response( $response ); 
     468                                $url = $this->blogs[$importing_blog]['posts_url']; 
    443469 
    444                                 // Extract the entries and send for insertion 
    445                                 preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches ); 
    446                                 if ( count( $matches[0] ) ) { 
    447                                         $entries = array_reverse($matches[0]); 
    448                                         foreach ( $entries as $entry ) { 
    449                                                 $entry = "<feed>$entry</feed>"; 
    450                                                 $AtomParser = new AtomParser(); 
    451                                                 $AtomParser->parse( $entry ); 
    452                                                 $result = $this->import_post($AtomParser->entry); 
    453                                                 if ( is_wp_error( $result ) ) 
    454                                                         return $result; 
    455                                                 unset($AtomParser); 
     470                                $response = $this->oauth_get( $url, array('max-results'=>MAX_RESULTS, 'start-index'=>$start_index) ); 
     471                                 
     472                                if ($response == false) break; 
     473                                                                 
     474                                // parse the feed 
     475                                $feed = new SimplePie(); 
     476                $feed->set_item_class('WP_SimplePie_Blog_Item'); 
     477                $feed->set_sanitize_class('Blogger_Importer_Sanitize'); 
     478                $feed->set_raw_data($response); 
     479                                $feed->init(); 
     480                 
     481                                foreach ( $feed->get_items() as $item ) { 
     482                                     
     483                                        $blogentry = new BloggerEntry(); 
     484                                         
     485                                        $blogentry->id = $item->get_id(); 
     486                                        $blogentry->published = $item->get_published(); 
     487                                        $blogentry->updated = $item->get_updated(); 
     488                    $blogentry->isDraft = $item->get_draft_status($item); 
     489                                        $blogentry->title = $item->get_title(); 
     490                                        $blogentry->content = $item->get_content(); 
     491                                        $blogentry->author = $item->get_author()->get_name(); 
     492                                         
     493                                        $linktypes = array('replies','edit','self','alternate'); 
     494                                        foreach ($linktypes as $type) { 
     495                                                $links = $item->get_links($type); 
     496                         
     497                        if (!is_null($links)) { 
     498                                                foreach ($links as $link) { 
     499                                                        $blogentry->links[] = array( 'rel' => $type, 'href' => $link ); 
     500                                                } 
     501                        } 
    456502                                        } 
    457                                 } else break; 
    458  
    459                                 // Get the 'previous' query string which we'll use on the next iteration 
    460                                 $query = ''; 
    461                                 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches); 
    462                                 if ( count( $matches[1] ) ) 
    463                                         foreach ( $matches[1] as $match ) 
    464                                                 if ( preg_match('/rel=.previous./', $match) ) 
    465                                                         $query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') ); 
    466  
    467                                 if ( $query ) { 
    468                                         parse_str($query, $q); 
    469                                         $this->blogs[$importing_blog]['posts_start_index'] = (int) $q['start-index']; 
    470                                 } else 
    471                                         $this->blogs[$importing_blog]['posts_start_index'] = 0; 
     503                                         
     504                                        $cats = $item->get_categories(); 
     505                     
     506                    if (!is_null($cats)) { 
     507                                        foreach ($cats as $cat) { 
     508                                                $blogentry->categories[] = $cat->term; 
     509                                        } 
     510                    } 
     511                                         
     512                                        $result = $this->import_post($blogentry); 
     513                     
     514                    //Ref: Not importing properly http://core.trac.wordpress.org/ticket/19096   
     515                    //Simplified this section to count what is loaded rather than parsing the results again 
     516                    $start_index++; 
     517                                } 
     518                                 
     519                $this->blogs[$importing_blog]['posts_start_index'] = $start_index; 
     520                 
    472521                                $this->save_vars(); 
    473                         } while ( !empty( $query ) && $this->have_time() ); 
     522                 
     523                        } while ( $this->blogs[$importing_blog]['total_posts'] >$start_index && $this->have_time() ); //have time function will "die" if it's out of time 
    474524                } 
    475525 
    476                 $total_results = $this->get_total_results( 'comments', $blog['host'] ); 
    477                 $this->blogs[$importing_blog]['total_comments'] = $total_results; 
    478  
    479                 if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) ) 
     526         
     527        if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) ) 
    480528                        $start_index = (int) $this->blogs[$importing_blog]['comments_start_index']; 
    481                 elseif ( $total_results > MAX_RESULTS ) 
    482                         $start_index = $total_results - MAX_RESULTS + 1; 
    483                 else 
    484                         $start_index = 1; 
     529        else 
     530            $start_index = 1;  
     531         
     532                if ( $start_index > 0 && $this->blogs[$importing_blog]['total_comments'] > 0) { 
    485533 
    486                 if ( $start_index > 0 ) { 
    487                         // Grab all the comments 
    488534                        $this->blogs[$importing_blog]['mode'] = 'comments'; 
    489                         $query = "start-index=$start_index&max-results=" . MAX_RESULTS; 
    490535                        do { 
    491536                                $index = $struct = $entries = array(); 
    492                                 $headers = array( 
    493                                         "GET /feeds/comments/default?$query HTTP/1.0", 
    494                                         "Host: {$blog['host']}", 
    495                                         "Authorization: AuthSub token=\"$this->token\"" 
    496                                 ); 
    497                                 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
    498                                 $sock = $this->_get_blogger_sock( $blog['host'] ); 
    499                                 if ( ! $sock ) return; // TODO: Error handling 
    500                                 $response = $this->_txrx( $sock, $request ); 
    501537 
    502                                 $response = $this->parse_response( $response ); 
    503  
    504                                 // Extract the comments and send for insertion 
    505                                 preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches ); 
    506                                 if ( count( $matches[0] ) ) { 
    507                                         $entries = array_reverse( $matches[0] ); 
    508                                         foreach ( $entries as $entry ) { 
    509                                                 $entry = "<feed>$entry</feed>"; 
    510                                                 $AtomParser = new AtomParser(); 
    511                                                 $AtomParser->parse( $entry ); 
    512                                                 $this->import_comment($AtomParser->entry); 
    513                                                 unset($AtomParser); 
    514                                         } 
    515                                 } 
    516  
    517                                 // Get the 'previous' query string which we'll use on the next iteration 
    518                                 $query = ''; 
    519                                 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches); 
    520                                 if ( count( $matches[1] ) ) 
    521                                         foreach ( $matches[1] as $match ) 
    522                                                 if ( preg_match('/rel=.previous./', $match) ) 
    523                                                         $query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') ); 
    524  
    525                                 parse_str($query, $q); 
    526  
    527                                 $this->blogs[$importing_blog]['comments_start_index'] = (int) $q['start-index']; 
     538                //So we can link up the comments as we go we need to load them in reverse order  
     539                //Reverse the start index as the GData Blogger feed can't be sorted  
     540                $batch = ((floor(($this->blogs[$importing_blog]['total_comments'] - $start_index)/MAX_RESULTS)*MAX_RESULTS)+1); 
     541                                               
     542                                $response = $this->oauth_get( $this->blogs[$importing_blog]['comments_url'], array('max-results'=>MAX_RESULTS, 'start-index'=>$batch) ); 
     543                                 
     544                                // parse the feed 
     545                                $feed = new SimplePie(); 
     546                $feed->set_item_class('WP_SimplePie_Blog_Item'); 
     547                // Use the standard "stricter" sanitize class for comments 
     548                                $feed->set_raw_data($response); 
     549                                $feed->init(); 
     550                 
     551                //Reverse the batch so we load the oldest comments first and hence can link up nested comments 
     552                $comments = array_reverse($feed->get_items()); 
     553                 
     554                if (!is_null($comments)) { 
     555                                foreach ($comments  as $item ) { 
     556   
     557                                        $blogentry = new BloggerEntry(); 
     558                        $blogentry->id = $item->get_id();  
     559                                        $blogentry->updated = $item->get_updated(); 
     560                                        $blogentry->content = $item->get_content(); 
     561                                        $blogentry->author = $item->get_author()->get_name(); 
     562                                        $blogentry->authoruri = $item->get_author()->get_link(); 
     563                                        $blogentry->authoremail = $item->get_author()->get_email(); 
     564     
     565                                        $temp = $item->get_item_tags('http://purl.org/syndication/thread/1.0','in-reply-to'); 
     566     
     567                                        foreach ($temp as $t) { 
     568                                                if ( isset( $t['attribs']['']['source'] ) ) { 
     569                                                        $blogentry->source = $t['attribs']['']['source']; 
     570                                                } 
     571                                        } 
     572                         
     573                        //Get the links 
     574                        $linktypes = array('edit','self','alternate','related'); 
     575                                        foreach ($linktypes as $type) { 
     576                                                $links = $item->get_links($type); 
     577                            if (!is_null($links)) { 
     578                                                        foreach ($links as $link) { 
     579                                                                $blogentry->links[] = array( 'rel' => $type, 'href' => $link ); 
     580                                                        } 
     581                            } 
     582                                        } 
     583                                         
     584                                        $this->import_comment($blogentry); 
     585                        $start_index++; 
     586                                } 
     587                } 
     588                                 
     589                                $this->blogs[$importing_blog]['comments_start_index'] = $start_index; 
    528590                                $this->save_vars(); 
    529                         } while ( !empty( $query ) && $this->have_time() ); 
     591                        } while ( $this->blogs[$importing_blog]['total_comments'] > $start_index && $this->have_time() ); 
    530592                } 
     593 
    531594                $this->blogs[$importing_blog]['mode'] = 'authors'; 
    532595                $this->save_vars(); 
     596 
    533597                if ( !$this->blogs[$importing_blog]['posts_done'] && !$this->blogs[$importing_blog]['comments_done'] ) 
    534                         die('nothing'); 
     598                        self::ajax_die('nothing'); 
     599 
    535600                do_action('import_done', 'blogger'); 
    536                 die('done'); 
     601                self::ajax_die('done'); 
    537602        } 
    538603 
    539         function convert_date( $date ) { 
    540             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); 
    541             $offset = iso8601_timezone_to_offset( $date_bits[7] ); 
    542                 $timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]); 
    543                 $timestamp -= $offset; // Convert from Blogger local time to GMT 
    544                 $timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time 
    545                 return gmdate('Y-m-d H:i:s', $timestamp); 
    546         } 
    547  
    548604        function no_apos( $string ) { 
    549605                return str_replace( '&apos;', "'", $string); 
    550606        } 
     
    559615 
    560616        function import_post( $entry ) { 
    561617                global $importing_blog; 
    562  
    563                 // The old permalink is all Blogger gives us to link comments to their posts. 
    564                 if ( isset( $entry->draft ) ) 
    565                         $rel = 'self'; 
    566                 else 
    567                         $rel = 'alternate'; 
    568                 foreach ( $entry->links as $link ) { 
    569                         if ( $link['rel'] == $rel ) { 
     618         
     619                foreach ( $entry->links as $link ) {                     
     620                        // save the self link as meta 
     621                        if ( $link['rel'] == 'self' ) { 
     622                                $postself = $link['href']; 
    570623                                $parts = parse_url( $link['href'] ); 
    571624                                $entry->old_permalink = $parts['path']; 
    572                                 break; 
    573625                        } 
     626 
     627            // get the old URI for the page when available 
     628            if ( $link['rel'] == 'alternate' ) { 
     629                                $parts = parse_url( $link['href'] ); 
     630                                $entry->bookmark = $parts['path']; 
     631                        } 
     632             
     633                        // save the replies feed link as meta (ignore the comment form one) 
     634                        if ( $link['rel'] == 'replies' && false === strpos($link['href'], '#comment-form') ) { 
     635                                $postreplies = $link['href']; 
     636                        } 
    574637                } 
    575  
    576                 $post_date    = $this->convert_date( $entry->published ); 
     638                 
     639        //Check if we are double cleaning here? Does the Simplepie already do all this? 
     640                $post_date    =  $entry->published; 
    577641                $post_content = trim( addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) ) ); 
    578642                $post_title   = trim( addslashes( $this->no_apos( $this->min_whitespace( $entry->title ) ) ) ); 
    579                 $post_status  = isset( $entry->draft ) ? 'draft' : 'publish'; 
     643         
     644                $post_status  = $entry->isDraft ? 'draft' : 'publish'; 
    580645 
    581                 // Clean up content 
    582                 $post_content = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_content); 
    583                 $post_content = str_replace('<br>', '<br />', $post_content); 
    584                 $post_content = str_replace('<hr>', '<hr />', $post_content); 
     646                // N.B. Clean up of $post_content is now part of the sanitize class 
    585647 
    586648                // Checks for duplicates 
    587649                if ( isset( $this->blogs[$importing_blog]['posts'][$entry->old_permalink] ) ) { 
    588                         ++$this->blogs[$importing_blog]['posts_skipped']; 
     650                        $this->blogs[$importing_blog]['posts_skipped']++; 
    589651                } elseif ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) { 
    590652                        $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id; 
    591                         ++$this->blogs[$importing_blog]['posts_skipped']; 
     653                        $this->blogs[$importing_blog]['posts_skipped']++; 
    592654                } else { 
    593655                        $post = compact('post_date', 'post_content', 'post_title', 'post_status'); 
    594656 
     
    602664 
    603665                        add_post_meta( $post_id, 'blogger_blog', $this->blogs[$importing_blog]['host'], true ); 
    604666                        add_post_meta( $post_id, 'blogger_author', $author, true ); 
    605                         add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true ); 
     667             
     668            //Use the page id if available or the blogger internal id if it's a draft 
     669                if ( $entry->isDraft | !isset($entry->bookmark) ) 
     670                             add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true ); 
     671            else 
     672                             add_post_meta( $post_id, 'blogger_permalink', $entry->bookmark, true ); 
     673                  
     674                        add_post_meta( $post_id, '_blogger_self', $postself, true ); 
     675            //Undefined variable: postreplies  
     676                        //add_post_meta( $post_id, '_blogger_replies', $postreplies, true ); 
    606677 
    607678                        $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id; 
    608                         ++$this->blogs[$importing_blog]['posts_done']; 
     679             
     680                        $this->blogs[$importing_blog]['posts_done']++; 
    609681                } 
    610682                $this->save_vars(); 
    611683                return; 
     
    613685 
    614686        function import_comment( $entry ) { 
    615687                global $importing_blog; 
    616  
     688                 
     689                $parts = parse_url( $entry->source ); 
     690                $entry->old_post_permalink = $parts['path'];          //Will be something like this '/feeds/417730729915399755/posts/default/8397846992898424746' 
     691                 
    617692                // Drop the #fragment and we have the comment's old post permalink. 
    618693                foreach ( $entry->links as $link ) { 
    619694                        if ( $link['rel'] == 'alternate' ) { 
    620695                                $parts = parse_url( $link['href'] ); 
    621696                                $entry->old_permalink = $parts['fragment']; 
    622                                 $entry->old_post_permalink = $parts['path']; 
    623                                 break; 
    624697                        } 
     698            //Parent post for nested links 
     699            if ( $link['rel'] == 'related' ) { 
     700                $parts = parse_url( $link['href'] ); 
     701                                $entry->related = $parts['path']; 
     702            } 
     703            if ( $link['rel'] == 'self' ) { 
     704                $parts = parse_url( $link['href'] ); 
     705                                $entry->self = $parts['path']; 
     706            }             
    625707                } 
     708         
     709        //Check for duplicated cleanup here 
     710        $comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink]; 
     711                $comment_author  = addslashes( $this->no_apos( strip_tags( $entry->author ) ) ); 
     712                $comment_author_url = addslashes( $this->no_apos( strip_tags( $entry->authoruri ) ) ); 
     713                $comment_author_email = addslashes( $this->no_apos( strip_tags( $entry->authoremail ) ) ); 
     714                $comment_date    = $entry->updated; 
    626715 
    627                 $comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink]; 
    628                 preg_match('#<name>(.+?)</name>.*(?:\<uri>(.+?)</uri>)?#', $entry->author, $matches); 
    629                 $comment_author  = addslashes( $this->no_apos( strip_tags( (string) $matches[1] ) ) ); 
    630                 $comment_author_url = addslashes( $this->no_apos( strip_tags( (string) $matches[2] ) ) ); 
    631                 $comment_date    = $this->convert_date( $entry->updated ); 
     716                // Clean up content 
     717        // Again, check if the Simplepie is already handling all this stuff 
    632718                $comment_content = addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) ); 
    633  
    634                 // Clean up content 
    635719                $comment_content = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $comment_content); 
    636720                $comment_content = str_replace('<br>', '<br />', $comment_content); 
    637721                $comment_content = str_replace('<hr>', '<hr />', $comment_content); 
    638  
    639                 // Checks for duplicates 
    640                 if ( 
    641                         isset( $this->blogs[$importing_blog]['comments'][$entry->old_permalink] ) || 
    642                         comment_exists( $comment_author, $comment_date ) 
    643                 ) { 
    644                         ++$this->blogs[$importing_blog]['comments_skipped']; 
    645                 } else { 
    646                         $comment = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_date', 'comment_content'); 
    647  
    648                         $comment = wp_filter_comment($comment); 
    649                         $comment_id = wp_insert_comment($comment); 
    650  
    651                         $this->blogs[$importing_blog]['comments'][$entry->old_permalink] = $comment_id; 
    652  
    653                         ++$this->blogs[$importing_blog]['comments_done']; 
    654                 } 
     722         
     723        // Nested comment? 
     724        if (!is_null($entry->related)) { 
     725            $comment_parent = $this->blogs[$importing_blog]['comments'][$entry->related]; 
     726        } 
     727         
     728        // if the post does not exist then we need stop and not add the comment 
     729        if ($comment_post_ID != 0) { 
     730                // Checks for duplicates 
     731                if ( 
     732                        isset( $this->blogs[$importing_blog][$entry->id] ) || 
     733                        $this->comment_exists($comment_post_ID, $comment_author, $comment_date ) 
     734                ) {                
     735                        $this->blogs[$importing_blog]['comments_skipped']++; 
     736                } else { 
     737                        $comment = compact('comment_post_ID', 'comment_author', 'comment_author_url','comment_author_email', 'comment_date', 'comment_content','comment_parent'); 
     738     
     739                        $comment = wp_filter_comment($comment); 
     740                        $comment_id = wp_insert_comment($comment); 
     741                 
     742                        $this->blogs[$importing_blog]['comments'][$entry->id] = $comment_id; 
     743                $this->blogs[$importing_blog]['comments'][$entry->self] = $comment_id; //For nested comments 
     744     
     745                        $this->blogs[$importing_blog]['comments_done']++; 
     746                } 
     747        } 
     748        else { 
     749            $this->blogs[$importing_blog]['comments_skipped']++; 
     750        } 
    655751                $this->save_vars(); 
    656752        } 
     753     
     754    function ajax_die($data){ 
     755        ob_clean(); //Discard any debug messages or other fluff already sent 
     756        header('Content-Type: text/plain'); 
     757                die($data); 
     758    } 
    657759 
     760 
    658761        function get_js_status($blog = false) { 
    659762                global $importing_blog; 
    660763                if ( $blog === false ) 
    661764                        $blog = $this->blogs[$importing_blog]; 
    662765                else 
    663766                        $blog = $this->blogs[$blog]; 
     767             
    664768                $p1 = isset( $blog['posts_done'] ) ? (int) $blog['posts_done'] : 0; 
    665769                $p2 = isset( $blog['total_posts'] ) ? (int) $blog['total_posts'] : 0; 
     770        $p3 = isset( $blog['posts_skipped'] ) ? (int) $blog['posts_skipped'] : 0; 
    666771                $c1 = isset( $blog['comments_done'] ) ? (int) $blog['comments_done'] : 0; 
    667772                $c2 = isset( $blog['total_comments'] ) ? (int) $blog['total_comments'] : 0; 
    668                 return "{p1:$p1,p2:$p2,c1:$c1,c2:$c2}"; 
     773        $c3 = isset( $blog['comments_skipped'] ) ? (int) $blog['comments_skipped'] : 0; 
     774        return "{p1:$p1,p2:$p2,p3:$p3,c1:$c1,c2:$c2,c3:$c3}"; 
    669775        } 
    670776 
    671777        function get_author_form($blog = false) { 
     
    688794                $mapthis = __('Blogger username', 'blogger-importer'); 
    689795                $tothis = __('WordPress login', 'blogger-importer'); 
    690796                $submit = esc_js( __('Save Changes', 'blogger-importer') ); 
     797        $rows = ''; 
    691798 
    692799                foreach ( $blog['authors'] as $i => $author ) 
    693800                        $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>"; 
     
    698805        function get_user_options($current) { 
    699806                global $importer_users; 
    700807                if ( ! isset( $importer_users ) ) 
    701                         $importer_users = (array) get_users_of_blog(); 
     808            $importer_users = (array) get_users();  //Function: get_users_of_blog() Deprecated in version 3.1. Use get_users() instead. 
    702809 
     810        $options = ''; 
     811 
    703812                foreach ( $importer_users as $user ) { 
    704                         $sel = ( $user->user_id == $current ) ? " selected='selected'" : ''; 
    705                         $options .= "<option value='$user->user_id'$sel>$user->display_name</option>"; 
     813                        $sel = ( $user->ID == $current ) ? " selected='selected'" : ''; 
     814                        $options .= "<option value='$user->ID'$sel>$user->display_name</option>"; 
    706815                } 
    707816 
    708817                return $options; 
     
    710819 
    711820        function save_authors() { 
    712821                global $importing_blog, $wpdb; 
     822        $blog =& $this->blogs[$importing_blog]; //Get a reference to blogs so we don't have to write it longhand 
     823                         
    713824                $authors = (array) $_POST['authors']; 
    714825 
    715                 $host = $this->blogs[$importing_blog]['host']; 
     826                $host = $blog['host']; 
    716827 
    717828                // Get an array of posts => authors 
    718829                $post_ids = (array) $wpdb->get_col( $wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'blogger_blog' AND meta_value = %s", $host) ); 
     
    725836                        $user_id = (int) $user_id; 
    726837 
    727838                        // Skip authors that haven't been changed 
    728                         if ( $user_id == $this->blogs[$importing_blog]['authors'][$author][1] ) 
     839                        if ( $user_id == $blog['authors'][$author][1] ) 
    729840                                continue; 
    730841 
    731842                        // Get a list of the selected author's posts 
    732                         $post_ids = (array) array_keys( $authors_posts, $this->blogs[$importing_blog]['authors'][$author][0] ); 
     843                        $post_ids = (array) array_keys( $authors_posts, $blog['authors'][$author][0] ); 
    733844                        $post_ids = join( ',', $post_ids); 
    734845 
    735846                        $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET post_author = %d WHERE id IN ($post_ids)", $user_id) ); 
    736                         $this->blogs[$importing_blog]['authors'][$author][1] = $user_id; 
     847                        $blog['authors'][$author][1] = $user_id; 
    737848                } 
    738849                $this->save_vars(); 
    739850 
    740851                wp_redirect('edit.php'); 
    741852        } 
    742853 
    743         function _get_auth_sock() { 
    744                 // Connect to https://www.google.com 
    745                 if ( !$sock = @ fsockopen('ssl://www.google.com', 443, $errno, $errstr) ) { 
    746                         $this->uh_oh( 
    747                                 __('Could not connect to https://www.google.com', 'blogger-importer'), 
    748                                 __('There was a problem opening a secure connection to Google. This is what went wrong:', 'blogger-importer'), 
    749                                 "$errstr ($errno)" 
    750                         ); 
    751                         return false; 
    752                 } 
    753                 return $sock; 
    754         } 
    755  
    756         function _get_blogger_sock($host = 'www2.blogger.com') { 
    757                 if ( !$sock = @ fsockopen($host, 80, $errno, $errstr) ) { 
    758                         $this->uh_oh( 
    759                                 sprintf( __('Could not connect to %s', 'blogger-importer'), $host ), 
    760                                 __('There was a problem opening a connection to Blogger. This is what went wrong:', 'blogger-importer'), 
    761                                 "$errstr ($errno)" 
    762                         ); 
    763                         return false; 
    764                 } 
    765                 return $sock; 
    766         } 
    767  
    768         function _txrx( $sock, $request ) { 
    769                 fwrite( $sock, $request ); 
    770                 while ( ! feof( $sock ) ) 
    771                         $response .= @ fread ( $sock, 8192 ); 
    772                 fclose( $sock ); 
    773                 return $response; 
    774         } 
    775  
    776         function revoke($token) { 
    777                 $headers = array( 
    778                         "GET /accounts/AuthSubRevokeToken HTTP/1.0", 
    779                         "Authorization: AuthSub token=\"$token\"" 
    780                 ); 
    781                 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
    782                 $sock = $this->_get_auth_sock( ); 
    783                 if ( ! $sock ) return false; 
    784                 $this->_txrx( $sock, $request ); 
    785         } 
    786  
    787854        function restart() { 
    788855                global $wpdb; 
    789856                $options = get_option( 'blogger_importer' ); 
    790857 
    791                 if ( isset( $options['token'] ) ) 
    792                         $this->revoke( $options['token'] ); 
    793  
    794858                delete_option('blogger_importer'); 
    795859                $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'blogger_author'"); 
    796860                wp_redirect('?import=blogger'); 
    797861        } 
    798862 
    799         // Returns associative array of code, header, cookies, body. Based on code from php.net. 
    800         function parse_response($this_response) { 
    801                 // Split response into header and body sections 
    802                 list($response_headers, $response_body) = explode("\r\n\r\n", $this_response, 2); 
    803                 $response_header_lines = explode("\r\n", $response_headers); 
    804  
    805                 // First line of headers is the HTTP response code 
    806                 $http_response_line = array_shift($response_header_lines); 
    807                 if (preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@',$http_response_line, $matches)) { $response_code = $matches[1]; } 
    808  
    809                 // put the rest of the headers in an array 
    810                 $response_header_array = array(); 
    811                 foreach($response_header_lines as $header_line) { 
    812                         list($header,$value) = explode(': ', $header_line, 2); 
    813                         $response_header_array[$header] .= $value."\n"; 
    814                 } 
    815  
    816                 $cookie_array = array(); 
    817                 $cookies = explode("\n", $response_header_array["Set-Cookie"]); 
    818                 foreach($cookies as $this_cookie) { array_push($cookie_array, "Cookie: ".$this_cookie); } 
    819  
    820                 return array("code" => $response_code, "header" => $response_header_array, "cookies" => $cookie_array, "body" => $response_body); 
    821         } 
    822  
    823863        // Step 9: Congratulate the user 
    824864        function congrats() { 
    825865                $blog = (int) $_GET['blog']; 
     
    836876        function start() { 
    837877                if ( isset($_POST['restart']) ) 
    838878                        $this->restart(); 
    839  
     879                         
    840880                $options = get_option('blogger_importer'); 
    841881 
    842882                if ( is_array($options) ) 
     
    849889                        $result = $this->import_blog( $blog ); 
    850890                        if ( is_wp_error( $result ) ) 
    851891                                echo $result->get_error_message(); 
    852                 } elseif ( isset($_GET['token']) ) 
     892                } elseif ( isset($_GET['token']) && isset($_GET['token_secret']) ) 
    853893                        $this->auth(); 
    854                 elseif ( isset($this->token) && $this->token_is_valid() ) 
     894                elseif ( isset($this->token) && isset($this->token_secret) ) 
    855895                        $this->show_blogs(); 
    856896                else 
    857897                        $this->greet(); 
     
    873913                return !empty($vars); 
    874914        } 
    875915 
    876         function admin_head() { 
    877 ?> 
    878 <style type="text/css"> 
    879 td { text-align: center; line-height: 2em;} 
    880 thead td { font-weight: bold; } 
    881 .bar { 
    882         width: 200px; 
    883         text-align: left; 
    884         line-height: 2em; 
    885         padding: 0px; 
    886 } 
    887 .ind { 
    888         position: absolute; 
    889         background-color: #83B4D8; 
    890         width: 1px; 
    891         z-index: 9; 
    892 } 
    893 .stat { 
    894         z-index: 10; 
    895         position: relative; 
    896         text-align: center; 
    897 } 
    898 </style> 
    899 <?php 
    900         } 
     916    function comment_exists($post_id, $comment_author, $comment_date) { 
     917    //Do we have 2 comments for the same author at the same time, on the same post? 
     918    //returns comment id 
     919        global $wpdb; 
    901920 
    902         function Blogger_Import() { 
    903                 global $importer_started; 
    904                 $importer_started = time(); 
    905                 if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) { 
    906                         wp_enqueue_script('jquery'); 
    907                         add_action('admin_head', array(&$this, 'admin_head')); 
    908                 } 
    909         } 
    910 } 
     921        $comment_author = stripslashes($comment_author); 
     922        $comment_date = stripslashes($comment_date); 
    911923 
    912 $blogger_import = new Blogger_Import(); 
     924        return $wpdb->get_var( $wpdb->prepare("SELECT comment_ID FROM $wpdb->comments 
     925                        WHERE comment_post_ID = %s and comment_author = %s AND comment_date = %s", $post_id, $comment_author, $comment_date) ); 
     926    } 
    913927 
    914 register_importer('blogger', __('Blogger', 'blogger-importer'), __('Import posts, comments, and users from a Blogger blog.', 'blogger-importer'), array ($blogger_import, 'start')); 
     928} 
    915929 
    916 class AtomEntry { 
     930class BloggerEntry { 
    917931        var $links = array(); 
    918932        var $categories = array(); 
    919933} 
    920934 
    921 class AtomParser { 
    922  
    923         var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights'); 
    924         var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft','author'); 
    925  
    926         var $depth = 0; 
    927         var $indent = 2; 
    928         var $in_content; 
    929         var $ns_contexts = array(); 
    930         var $ns_decls = array(); 
    931         var $is_xhtml = false; 
    932         var $skipped_div = false; 
    933  
    934         var $entry; 
    935  
    936         function AtomParser() { 
    937                 $this->entry = new AtomEntry(); 
    938         } 
    939  
    940         function _map_attrs_func( $k, $v ) { 
    941                 return "$k=\"$v\""; 
    942         } 
    943  
    944         function _map_xmlns_func( $p, $n ) { 
    945                 $xd = "xmlns"; 
    946                 if ( strlen( $n[0] ) > 0 ) 
    947                         $xd .= ":{$n[0]}"; 
    948  
    949                 return "{$xd}=\"{$n[1]}\""; 
    950         } 
    951  
    952         function parse($xml) { 
    953  
    954                 global $app_logging; 
    955                 array_unshift($this->ns_contexts, array()); 
    956  
    957                 $parser = xml_parser_create_ns(); 
    958                 xml_set_object($parser, $this); 
    959                 xml_set_element_handler($parser, "start_element", "end_element"); 
    960                 xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0); 
    961                 xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0); 
    962                 xml_set_character_data_handler($parser, "cdata"); 
    963                 xml_set_default_handler($parser, "_default"); 
    964                 xml_set_start_namespace_decl_handler($parser, "start_ns"); 
    965                 xml_set_end_namespace_decl_handler($parser, "end_ns"); 
    966  
    967                 $contents = ""; 
    968  
    969                 xml_parse($parser, $xml); 
    970  
    971                 xml_parser_free($parser); 
    972  
    973                 return true; 
    974         } 
    975  
    976         function start_element($parser, $name, $attrs) { 
    977  
    978                 $tag = array_pop(split(":", $name)); 
    979  
    980                 array_unshift($this->ns_contexts, $this->ns_decls); 
    981  
    982                 $this->depth++; 
    983  
    984                 if (!empty($this->in_content)) { 
    985                         $attrs_prefix = array(); 
    986  
    987                         // resolve prefixes for attributes 
    988                         foreach($attrs as $key => $value) { 
    989                                 $attrs_prefix[$this->ns_to_prefix($key)] = $this->xml_escape($value); 
    990                         } 
    991                         $attrs_str = join(' ', array_map( array( &$this, '_map_attrs_func' ), array_keys($attrs_prefix), array_values($attrs_prefix))); 
    992                         if (strlen($attrs_str) > 0) { 
    993                                 $attrs_str = " " . $attrs_str; 
    994                         } 
    995  
    996                         $xmlns_str = join(' ', array_map( array( &$this, '_map_xmlns_func' ), array_keys($this->ns_contexts[0]), array_values($this->ns_contexts[0]))); 
    997                         if (strlen($xmlns_str) > 0) { 
    998                                 $xmlns_str = " " . $xmlns_str; 
    999                         } 
    1000  
    1001                         // handle self-closing tags (case: a new child found right-away, no text node) 
    1002                         if (count($this->in_content) == 2) { 
    1003                                 array_push($this->in_content, ">"); 
    1004                         } 
    1005  
    1006                         array_push($this->in_content, "<". $this->ns_to_prefix($name) ."{$xmlns_str}{$attrs_str}"); 
    1007                 } else if (in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) { 
    1008                         $this->in_content = array(); 
    1009                         $this->is_xhtml = $attrs['type'] == 'xhtml'; 
    1010                         array_push($this->in_content, array($tag,$this->depth)); 
    1011                 } else if ($tag == 'link') { 
    1012                         array_push($this->entry->links, $attrs); 
    1013                 } else if ($tag == 'category') { 
    1014                         array_push($this->entry->categories, $attrs['term']); 
    1015                 } 
    1016  
    1017                 $this->ns_decls = array(); 
    1018         } 
    1019  
    1020         function end_element($parser, $name) { 
    1021  
    1022                 $tag = array_pop(split(":", $name)); 
    1023  
    1024                 if (!empty($this->in_content)) { 
    1025                         if ($this->in_content[0][0] == $tag && 
    1026                         $this->in_content[0][1] == $this->depth) { 
    1027                                 array_shift($this->in_content); 
    1028                                 if ($this->is_xhtml) { 
    1029                                         $this->in_content = array_slice($this->in_content, 2, count($this->in_content)-3); 
    1030                                 } 
    1031                                 $this->entry->$tag = join('',$this->in_content); 
    1032                                 $this->in_content = array(); 
    1033                         } else { 
    1034                                 $endtag = $this->ns_to_prefix($name); 
    1035                                 if (strpos($this->in_content[count($this->in_content)-1], '<' . $endtag) !== false) { 
    1036                                         array_push($this->in_content, "/>"); 
    1037                                 } else { 
    1038                                         array_push($this->in_content, "</$endtag>"); 
    1039                                 } 
    1040                         } 
    1041                 } 
    1042  
    1043                 array_shift($this->ns_contexts); 
    1044  
    1045                 #print str_repeat(" ", $this->depth * $this->indent) . "end_element('$name')" ."\n"; 
    1046  
    1047                 $this->depth--; 
    1048         } 
    1049  
    1050         function start_ns($parser, $prefix, $uri) { 
    1051                 #print str_repeat(" ", $this->depth * $this->indent) . "starting: " . $prefix . ":" . $uri . "\n"; 
    1052                 array_push($this->ns_decls, array($prefix,$uri)); 
    1053         } 
    1054  
    1055         function end_ns($parser, $prefix) { 
    1056                 #print str_repeat(" ", $this->depth * $this->indent) . "ending: #" . $prefix . "#\n"; 
    1057         } 
    1058  
    1059         function cdata($parser, $data) { 
    1060                 #print str_repeat(" ", $this->depth * $this->indent) . "data: #" . $data . "#\n"; 
    1061                 if (!empty($this->in_content)) { 
    1062                         // handle self-closing tags (case: text node found, need to close element started) 
    1063                         if (strpos($this->in_content[count($this->in_content)-1], '<') !== false) { 
    1064                                 array_push($this->in_content, ">"); 
    1065                         } 
    1066                         array_push($this->in_content, $this->xml_escape($data)); 
    1067                 } 
    1068         } 
    1069  
    1070         function _default($parser, $data) { 
    1071                 # when does this gets called? 
    1072         } 
    1073  
    1074  
    1075         function ns_to_prefix($qname) { 
    1076                 $components = split(":", $qname); 
    1077                 $name = array_pop($components); 
    1078  
    1079                 if (!empty($components)) { 
    1080                         $ns = join(":",$components); 
    1081                         foreach ($this->ns_contexts as $context) { 
    1082                                 foreach ($context as $mapping) { 
    1083                                         if ($mapping[1] == $ns && strlen($mapping[0]) > 0) { 
    1084                                                 return "$mapping[0]:$name"; 
    1085                                         } 
    1086                                 } 
    1087                         } 
    1088                 } 
    1089                 return $name; 
    1090         } 
    1091  
    1092         function xml_escape($string) 
    1093         { 
    1094                          return str_replace(array('&','"',"'",'<','>'), 
    1095                                 array('&amp;','&quot;','&apos;','&lt;','&gt;'), 
    1096                                 $string ); 
    1097         } 
    1098 } 
    1099935} // class_exists( 'WP_Importer' ) 
    1100936 
    1101937function blogger_importer_init() { 
    1102938    load_plugin_textdomain( 'blogger-importer', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); 
     939     
     940    $blogger_import = new Blogger_Import(); 
     941    register_importer('blogger', __('Blogger', 'blogger-importer'), __('Import categories, posts and comments then maps users from a Blogger blog.', 'blogger-importer'), array ($blogger_import, 'start')); 
     942 
    1103943} 
    1104 add_action( 'init', 'blogger_importer_init' ); 
     944add_action( 'admin_init', 'blogger_importer_init' ); 
  • oauth.php

     
     1<?php 
     2/* 
     3Original File from: http://oauth.googlecode.com/svn/code/php/ 
     4License: MIT License: 
     5 
     6The MIT License 
     7 
     8Copyright (c) 2007 Andy Smith 
     9 
     10Permission is hereby granted, free of charge, to any person obtaining a copy 
     11of this software and associated documentation files (the "Software"), to deal 
     12in the Software without restriction, including without limitation the rights 
     13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
     14copies of the Software, and to permit persons to whom the Software is 
     15furnished to do so, subject to the following conditions: 
     16 
     17The above copyright notice and this permission notice shall be included in 
     18all copies or substantial portions of the Software. 
     19 
     20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
     21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
     22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
     23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
     24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
     25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
     26THE SOFTWARE. 
     27 
     28 
     29Modified for use in WordPress by Otto 
     30 
     31Changes: All classes renamed to Blogger_* to prevent conflicts with other plugins/code using the same OAuth library. 
     32 
     33*/ 
     34// vim: foldmethod=marker 
     35 
     36/* Generic exception class 
     37 */ 
     38class Blogger_OAuthException extends Exception { 
     39  // pass 
     40} 
     41 
     42class Blogger_OAuthConsumer { 
     43  public $key; 
     44  public $secret; 
     45 
     46  function __construct($key, $secret, $callback_url=NULL) { 
     47    $this->key = $key; 
     48    $this->secret = $secret; 
     49    $this->callback_url = $callback_url; 
     50  } 
     51 
     52  function __toString() { 
     53    return "Blogger_OAuthConsumer[key=$this->key,secret=$this->secret]"; 
     54  } 
     55} 
     56 
     57class Blogger_OAuthToken { 
     58  // access tokens and request tokens 
     59  public $key; 
     60  public $secret; 
     61 
     62  /** 
     63   * key = the token 
     64   * secret = the token secret 
     65   */ 
     66  function __construct($key, $secret) { 
     67    $this->key = $key; 
     68    $this->secret = $secret; 
     69  } 
     70 
     71  /** 
     72   * generates the basic string serialization of a token that a server 
     73   * would respond to request_token and access_token calls with 
     74   */ 
     75  function to_string() { 
     76    return "oauth_token=" . 
     77           Blogger_OAuthUtil::urlencode_rfc3986($this->key) . 
     78           "&oauth_token_secret=" . 
     79           Blogger_OAuthUtil::urlencode_rfc3986($this->secret); 
     80  } 
     81 
     82  function __toString() { 
     83    return $this->to_string(); 
     84  } 
     85} 
     86 
     87/** 
     88 * A class for implementing a Signature Method 
     89 * See section 9 ("Signing Requests") in the spec 
     90 */ 
     91abstract class Blogger_OAuthSignatureMethod { 
     92  /** 
     93   * Needs to return the name of the Signature Method (ie HMAC-SHA1) 
     94   * @return string 
     95   */ 
     96  abstract public function get_name(); 
     97 
     98  /** 
     99   * Build up the signature 
     100   * NOTE: The output of this function MUST NOT be urlencoded. 
     101   * the encoding is handled in Blogger_OAuthRequest when the final 
     102   * request is serialized 
     103   * @param Blogger_OAuthRequest $request 
     104   * @param Blogger_OAuthConsumer $consumer 
     105   * @param Blogger_OAuthToken $token 
     106   * @return string 
     107   */ 
     108  abstract public function build_signature($request, $consumer, $token); 
     109 
     110  /** 
     111   * Verifies that a given signature is correct 
     112   * @param Blogger_OAuthRequest $request 
     113   * @param Blogger_OAuthConsumer $consumer 
     114   * @param Blogger_OAuthToken $token 
     115   * @param string $signature 
     116   * @return bool 
     117   */ 
     118  public function check_signature($request, $consumer, $token, $signature) { 
     119    $built = $this->build_signature($request, $consumer, $token); 
     120 
     121    // Check for zero length, although unlikely here 
     122    if (strlen($built) == 0 || strlen($signature) == 0) { 
     123      return false; 
     124    } 
     125 
     126    if (strlen($built) != strlen($signature)) { 
     127      return false; 
     128    } 
     129 
     130    // Avoid a timing leak with a (hopefully) time insensitive compare 
     131    $result = 0; 
     132    for ($i = 0; $i < strlen($signature); $i++) { 
     133      $result |= ord($built{$i}) ^ ord($signature{$i}); 
     134    } 
     135 
     136    return $result == 0; 
     137  } 
     138} 
     139 
     140/** 
     141 * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]  
     142 * where the Signature Base String is the text and the key is the concatenated values (each first  
     143 * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'  
     144 * character (ASCII code 38) even if empty. 
     145 *   - Chapter 9.2 ("HMAC-SHA1") 
     146 */ 
     147class Blogger_OAuthSignatureMethod_HMAC_SHA1 extends Blogger_OAuthSignatureMethod { 
     148  function get_name() { 
     149    return "HMAC-SHA1"; 
     150  } 
     151 
     152  public function build_signature($request, $consumer, $token) { 
     153    $base_string = $request->get_signature_base_string(); 
     154    $request->base_string = $base_string; 
     155 
     156    $key_parts = array( 
     157      $consumer->secret, 
     158      ($token) ? $token->secret : "" 
     159    ); 
     160 
     161    $key_parts = Blogger_OAuthUtil::urlencode_rfc3986($key_parts); 
     162    $key = implode('&', $key_parts); 
     163 
     164    return base64_encode(hash_hmac('sha1', $base_string, $key, true)); 
     165  } 
     166} 
     167 
     168/** 
     169 * The PLAINTEXT method does not provide any security protection and SHOULD only be used  
     170 * over a secure channel such as HTTPS. It does not use the Signature Base String. 
     171 *   - Chapter 9.4 ("PLAINTEXT") 
     172 */ 
     173class Blogger_OAuthSignatureMethod_PLAINTEXT extends Blogger_OAuthSignatureMethod { 
     174  public function get_name() { 
     175    return "PLAINTEXT"; 
     176  } 
     177 
     178  /** 
     179   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and  
     180   * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is  
     181   * empty. The result MUST be encoded again. 
     182   *   - Chapter 9.4.1 ("Generating Signatures") 
     183   * 
     184   * Please note that the second encoding MUST NOT happen in the SignatureMethod, as 
     185   * Blogger_OAuthRequest handles this! 
     186   */ 
     187  public function build_signature($request, $consumer, $token) { 
     188    $key_parts = array( 
     189      $consumer->secret, 
     190      ($token) ? $token->secret : "" 
     191    ); 
     192 
     193    $key_parts = Blogger_OAuthUtil::urlencode_rfc3986($key_parts); 
     194    $key = implode('&', $key_parts); 
     195    $request->base_string = $key; 
     196 
     197    return $key; 
     198  } 
     199} 
     200 
     201/** 
     202 * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in  
     203 * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for  
     204 * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a  
     205 * verified way to the Service Provider, in a manner which is beyond the scope of this  
     206 * specification. 
     207 *   - Chapter 9.3 ("RSA-SHA1") 
     208 */ 
     209abstract class Blogger_OAuthSignatureMethod_RSA_SHA1 extends Blogger_OAuthSignatureMethod { 
     210  public function get_name() { 
     211    return "RSA-SHA1"; 
     212  } 
     213 
     214  // Up to the SP to implement this lookup of keys. Possible ideas are: 
     215  // (1) do a lookup in a table of trusted certs keyed off of consumer 
     216  // (2) fetch via http using a url provided by the requester 
     217  // (3) some sort of specific discovery code based on request 
     218  // 
     219  // Either way should return a string representation of the certificate 
     220  protected abstract function fetch_public_cert(&$request); 
     221 
     222  // Up to the SP to implement this lookup of keys. Possible ideas are: 
     223  // (1) do a lookup in a table of trusted certs keyed off of consumer 
     224  // 
     225  // Either way should return a string representation of the certificate 
     226  protected abstract function fetch_private_cert(&$request); 
     227 
     228  public function build_signature($request, $consumer, $token) { 
     229    $base_string = $request->get_signature_base_string(); 
     230    $request->base_string = $base_string; 
     231 
     232    // Fetch the private key cert based on the request 
     233    $cert = $this->fetch_private_cert($request); 
     234 
     235    // Pull the private key ID from the certificate 
     236    $privatekeyid = openssl_get_privatekey($cert); 
     237 
     238    // Sign using the key 
     239    $ok = openssl_sign($base_string, $signature, $privatekeyid); 
     240 
     241    // Release the key resource 
     242    openssl_free_key($privatekeyid); 
     243 
     244    return base64_encode($signature); 
     245  } 
     246 
     247  public function check_signature($request, $consumer, $token, $signature) { 
     248    $decoded_sig = base64_decode($signature); 
     249 
     250    $base_string = $request->get_signature_base_string(); 
     251 
     252    // Fetch the public key cert based on the request 
     253    $cert = $this->fetch_public_cert($request); 
     254 
     255    // Pull the public key ID from the certificate 
     256    $publickeyid = openssl_get_publickey($cert); 
     257 
     258    // Check the computed signature against the one passed in the query 
     259    $ok = openssl_verify($base_string, $decoded_sig, $publickeyid); 
     260 
     261    // Release the key resource 
     262    openssl_free_key($publickeyid); 
     263 
     264    return $ok == 1; 
     265  } 
     266} 
     267 
     268class Blogger_OAuthRequest { 
     269  protected $parameters; 
     270  protected $http_method; 
     271  protected $http_url; 
     272  // for debug purposes 
     273  public $base_string; 
     274  public static $version = '1.0'; 
     275  public static $POST_INPUT = 'php://input'; 
     276 
     277  function __construct($http_method, $http_url, $parameters=NULL) { 
     278    $parameters = ($parameters) ? $parameters : array(); 
     279    $parameters = array_merge( Blogger_OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); 
     280    $this->parameters = $parameters; 
     281    $this->http_method = $http_method; 
     282    $this->http_url = $http_url; 
     283  } 
     284 
     285 
     286  /** 
     287   * attempt to build up a request from what was passed to the server 
     288   */ 
     289  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) { 
     290    $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") 
     291              ? 'http' 
     292              : 'https'; 
     293    $http_url = ($http_url) ? $http_url : $scheme . 
     294                              '://' . $_SERVER['SERVER_NAME'] . 
     295                              ':' . 
     296                              $_SERVER['SERVER_PORT'] . 
     297                              $_SERVER['REQUEST_URI']; 
     298    $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD']; 
     299 
     300    // We weren't handed any parameters, so let's find the ones relevant to 
     301    // this request. 
     302    // If you run XML-RPC or similar you should use this to provide your own 
     303    // parsed parameter-list 
     304    if (!$parameters) { 
     305      // Find request headers 
     306      $request_headers = Blogger_OAuthUtil::get_headers(); 
     307 
     308      // Parse the query-string to find GET parameters 
     309      $parameters = Blogger_OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); 
     310 
     311      // It's a POST request of the proper content-type, so parse POST 
     312      // parameters and add those overriding any duplicates from GET 
     313      if ($http_method == "POST" 
     314          &&  isset($request_headers['Content-Type']) 
     315          && strstr($request_headers['Content-Type'], 
     316                     'application/x-www-form-urlencoded') 
     317          ) { 
     318        $post_data = Blogger_OAuthUtil::parse_parameters( 
     319          file_get_contents(self::$POST_INPUT) 
     320        ); 
     321        $parameters = array_merge($parameters, $post_data); 
     322      } 
     323 
     324      // We have a Authorization-header with Blogger_OAuth data. Parse the header 
     325      // and add those overriding any duplicates from GET or POST 
     326      if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'Blogger_OAuth ') { 
     327        $header_parameters = Blogger_OAuthUtil::split_header( 
     328          $request_headers['Authorization'] 
     329        ); 
     330        $parameters = array_merge($parameters, $header_parameters); 
     331      } 
     332 
     333    } 
     334 
     335    return new Blogger_OAuthRequest($http_method, $http_url, $parameters); 
     336  } 
     337 
     338  /** 
     339   * pretty much a helper function to set up the request 
     340   */ 
     341  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) { 
     342    $parameters = ($parameters) ?  $parameters : array(); 
     343    $defaults = array("oauth_version" => Blogger_OAuthRequest::$version, 
     344                      "oauth_nonce" => Blogger_OAuthRequest::generate_nonce(), 
     345                      "oauth_timestamp" => Blogger_OAuthRequest::generate_timestamp(), 
     346                      "oauth_consumer_key" => $consumer->key); 
     347    if ($token) 
     348      $defaults['oauth_token'] = $token->key; 
     349 
     350    $parameters = array_merge($defaults, $parameters); 
     351 
     352    return new Blogger_OAuthRequest($http_method, $http_url, $parameters); 
     353  } 
     354 
     355  public function set_parameter($name, $value, $allow_duplicates = true) { 
     356    if ($allow_duplicates && isset($this->parameters[$name])) { 
     357      // We have already added parameter(s) with this name, so add to the list 
     358      if (is_scalar($this->parameters[$name])) { 
     359        // This is the first duplicate, so transform scalar (string) 
     360        // into an array so we can add the duplicates 
     361        $this->parameters[$name] = array($this->parameters[$name]); 
     362      } 
     363 
     364      $this->parameters[$name][] = $value; 
     365    } else { 
     366      $this->parameters[$name] = $value; 
     367    } 
     368  } 
     369 
     370  public function get_parameter($name) { 
     371    return isset($this->parameters[$name]) ? $this->parameters[$name] : null; 
     372  } 
     373 
     374  public function get_parameters() { 
     375    return $this->parameters; 
     376  } 
     377 
     378  public function unset_parameter($name) { 
     379    unset($this->parameters[$name]); 
     380  } 
     381 
     382  /** 
     383   * The request parameters, sorted and concatenated into a normalized string. 
     384   * @return string 
     385   */ 
     386  public function get_signable_parameters() { 
     387    // Grab all parameters 
     388    $params = $this->parameters; 
     389 
     390    // Remove oauth_signature if present 
     391    // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") 
     392    if (isset($params['oauth_signature'])) { 
     393      unset($params['oauth_signature']); 
     394    } 
     395 
     396    return Blogger_OAuthUtil::build_http_query($params); 
     397  } 
     398 
     399  /** 
     400   * Returns the base string of this request 
     401   * 
     402   * The base string defined as the method, the url 
     403   * and the parameters (normalized), each urlencoded 
     404   * and the concated with &. 
     405   */ 
     406  public function get_signature_base_string() { 
     407    $parts = array( 
     408      $this->get_normalized_http_method(), 
     409      $this->get_normalized_http_url(), 
     410      $this->get_signable_parameters() 
     411    ); 
     412 
     413    $parts = Blogger_OAuthUtil::urlencode_rfc3986($parts); 
     414 
     415    return implode('&', $parts); 
     416  } 
     417 
     418  /** 
     419   * just uppercases the http method 
     420   */ 
     421  public function get_normalized_http_method() { 
     422    return strtoupper($this->http_method); 
     423  } 
     424 
     425  /** 
     426   * parses the url and rebuilds it to be 
     427   * scheme://host/path 
     428   */ 
     429  public function get_normalized_http_url() { 
     430    $parts = parse_url($this->http_url); 
     431 
     432    $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; 
     433    $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80'); 
     434    $host = (isset($parts['host'])) ? strtolower($parts['host']) : ''; 
     435    $path = (isset($parts['path'])) ? $parts['path'] : ''; 
     436 
     437    if (($scheme == 'https' && $port != '443') 
     438        || ($scheme == 'http' && $port != '80')) { 
     439      $host = "$host:$port"; 
     440    } 
     441    return "$scheme://$host$path"; 
     442  } 
     443 
     444  /** 
     445   * builds a url usable for a GET request 
     446   */ 
     447  public function to_url() { 
     448    $post_data = $this->to_postdata(); 
     449    $out = $this->get_normalized_http_url(); 
     450    if ($post_data) { 
     451      $out .= '?'.$post_data; 
     452    } 
     453    return $out; 
     454  } 
     455 
     456  /** 
     457   * builds the data one would send in a POST request 
     458   */ 
     459  public function to_postdata() { 
     460    return Blogger_OAuthUtil::build_http_query($this->parameters); 
     461  } 
     462 
     463  /** 
     464   * builds the Authorization: header 
     465   */ 
     466  public function to_header($realm=null) { 
     467    $first = true; 
     468        if($realm) { 
     469      $out = 'Authorization: Blogger_OAuth realm="' . Blogger_OAuthUtil::urlencode_rfc3986($realm) . '"'; 
     470      $first = false; 
     471    } else 
     472      $out = 'Authorization: Blogger_OAuth'; 
     473 
     474    $total = array(); 
     475    foreach ($this->parameters as $k => $v) { 
     476      if (substr($k, 0, 5) != "oauth") continue; 
     477      if (is_array($v)) { 
     478        throw new Blogger_OAuthException('Arrays not supported in headers'); 
     479      } 
     480      $out .= ($first) ? ' ' : ','; 
     481      $out .= Blogger_OAuthUtil::urlencode_rfc3986($k) . 
     482              '="' . 
     483              Blogger_OAuthUtil::urlencode_rfc3986($v) . 
     484              '"'; 
     485      $first = false; 
     486    } 
     487    return $out; 
     488  } 
     489 
     490  public function __toString() { 
     491    return $this->to_url(); 
     492  } 
     493 
     494 
     495  public function sign_request($signature_method, $consumer, $token) { 
     496    $this->set_parameter( 
     497      "oauth_signature_method", 
     498      $signature_method->get_name(), 
     499      false 
     500    ); 
     501    $signature = $this->build_signature($signature_method, $consumer, $token); 
     502    $this->set_parameter("oauth_signature", $signature, false); 
     503  } 
     504 
     505  public function build_signature($signature_method, $consumer, $token) { 
     506    $signature = $signature_method->build_signature($this, $consumer, $token); 
     507    return $signature; 
     508  } 
     509 
     510  /** 
     511   * util function: current timestamp 
     512   */ 
     513  private static function generate_timestamp() { 
     514    return time(); 
     515  } 
     516 
     517  /** 
     518   * util function: current nonce 
     519   */ 
     520  private static function generate_nonce() { 
     521    $mt = microtime(); 
     522    $rand = mt_rand(); 
     523 
     524    return md5($mt . $rand); // md5s look nicer than numbers 
     525  } 
     526} 
     527 
     528class Blogger_OAuthServer { 
     529  protected $timestamp_threshold = 300; // in seconds, five minutes 
     530  protected $version = '1.0';             // hi blaine 
     531  protected $signature_methods = array(); 
     532 
     533  protected $data_store; 
     534 
     535  function __construct($data_store) { 
     536    $this->data_store = $data_store; 
     537  } 
     538 
     539  public function add_signature_method($signature_method) { 
     540    $this->signature_methods[$signature_method->get_name()] = 
     541      $signature_method; 
     542  } 
     543 
     544  // high level functions 
     545 
     546  /** 
     547   * process a request_token request 
     548   * returns the request token on success 
     549   */ 
     550  public function fetch_request_token(&$request) { 
     551    $this->get_version($request); 
     552 
     553    $consumer = $this->get_consumer($request); 
     554 
     555    // no token required for the initial token request 
     556    $token = NULL; 
     557 
     558    $this->check_signature($request, $consumer, $token); 
     559 
     560    // Rev A change 
     561    $callback = $request->get_parameter('oauth_callback'); 
     562    $new_token = $this->data_store->new_request_token($consumer, $callback); 
     563 
     564    return $new_token; 
     565  } 
     566 
     567  /** 
     568   * process an access_token request 
     569   * returns the access token on success 
     570   */ 
     571  public function fetch_access_token(&$request) { 
     572    $this->get_version($request); 
     573 
     574    $consumer = $this->get_consumer($request); 
     575 
     576    // requires authorized request token 
     577    $token = $this->get_token($request, $consumer, "request"); 
     578 
     579    $this->check_signature($request, $consumer, $token); 
     580 
     581    // Rev A change 
     582    $verifier = $request->get_parameter('oauth_verifier'); 
     583    $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); 
     584 
     585    return $new_token; 
     586  } 
     587 
     588  /** 
     589   * verify an api call, checks all the parameters 
     590   */ 
     591  public function verify_request(&$request) { 
     592    $this->get_version($request); 
     593    $consumer = $this->get_consumer($request); 
     594    $token = $this->get_token($request, $consumer, "access"); 
     595    $this->check_signature($request, $consumer, $token); 
     596    return array($consumer, $token); 
     597  } 
     598 
     599  // Internals from here 
     600  /** 
     601   * version 1 
     602   */ 
     603  private function get_version(&$request) { 
     604    $version = $request->get_parameter("oauth_version"); 
     605    if (!$version) { 
     606      // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.  
     607      // Chapter 7.0 ("Accessing Protected Ressources") 
     608      $version = '1.0'; 
     609    } 
     610    if ($version !== $this->version) { 
     611      throw new Blogger_OAuthException("Blogger_OAuth version '$version' not supported"); 
     612    } 
     613    return $version; 
     614  } 
     615 
     616  /** 
     617   * figure out the signature with some defaults 
     618   */ 
     619  private function get_signature_method($request) { 
     620    $signature_method = $request instanceof Blogger_OAuthRequest  
     621        ? $request->get_parameter("oauth_signature_method") 
     622        : NULL; 
     623 
     624    if (!$signature_method) { 
     625      // According to chapter 7 ("Accessing Protected Ressources") the signature-method 
     626      // parameter is required, and we can't just fallback to PLAINTEXT 
     627      throw new Blogger_OAuthException('No signature method parameter. This parameter is required'); 
     628    } 
     629 
     630    if (!in_array($signature_method, 
     631                  array_keys($this->signature_methods))) { 
     632      throw new Blogger_OAuthException( 
     633        "Signature method '$signature_method' not supported " . 
     634        "try one of the following: " . 
     635        implode(", ", array_keys($this->signature_methods)) 
     636      ); 
     637    } 
     638    return $this->signature_methods[$signature_method]; 
     639  } 
     640 
     641  /** 
     642   * try to find the consumer for the provided request's consumer key 
     643   */ 
     644  private function get_consumer($request) { 
     645    $consumer_key = $request instanceof Blogger_OAuthRequest  
     646        ? $request->get_parameter("oauth_consumer_key") 
     647        : NULL; 
     648 
     649    if (!$consumer_key) { 
     650      throw new Blogger_OAuthException("Invalid consumer key"); 
     651    } 
     652 
     653    $consumer = $this->data_store->lookup_consumer($consumer_key); 
     654    if (!$consumer) { 
     655      throw new Blogger_OAuthException("Invalid consumer"); 
     656    } 
     657 
     658    return $consumer; 
     659  } 
     660 
     661  /** 
     662   * try to find the token for the provided request's token key 
     663   */ 
     664  private function get_token($request, $consumer, $token_type="access") { 
     665    $token_field = $request instanceof Blogger_OAuthRequest 
     666         ? $request->get_parameter('oauth_token') 
     667         : NULL; 
     668 
     669    $token = $this->data_store->lookup_token( 
     670      $consumer, $token_type, $token_field 
     671    ); 
     672    if (!$token) { 
     673      throw new Blogger_OAuthException("Invalid $token_type token: $token_field"); 
     674    } 
     675    return $token; 
     676  } 
     677 
     678  /** 
     679   * all-in-one function to check the signature on a request 
     680   * should guess the signature method appropriately 
     681   */ 
     682  private function check_signature($request, $consumer, $token) { 
     683    // this should probably be in a different method 
     684    $timestamp = $request instanceof Blogger_OAuthRequest 
     685        ? $request->get_parameter('oauth_timestamp') 
     686        : NULL; 
     687    $nonce = $request instanceof Blogger_OAuthRequest 
     688        ? $request->get_parameter('oauth_nonce') 
     689        : NULL; 
     690 
     691    $this->check_timestamp($timestamp); 
     692    $this->check_nonce($consumer, $token, $nonce, $timestamp); 
     693 
     694    $signature_method = $this->get_signature_method($request); 
     695 
     696    $signature = $request->get_parameter('oauth_signature'); 
     697    $valid_sig = $signature_method->check_signature( 
     698      $request, 
     699      $consumer, 
     700      $token, 
     701      $signature 
     702    ); 
     703 
     704    if (!$valid_sig) { 
     705      throw new Blogger_OAuthException("Invalid signature"); 
     706    } 
     707  } 
     708 
     709  /** 
     710   * check that the timestamp is new enough 
     711   */ 
     712  private function check_timestamp($timestamp) { 
     713    if( ! $timestamp ) 
     714      throw new Blogger_OAuthException( 
     715        'Missing timestamp parameter. The parameter is required' 
     716      ); 
     717     
     718    // verify that timestamp is recentish 
     719    $now = time(); 
     720    if (abs($now - $timestamp) > $this->timestamp_threshold) { 
     721      throw new Blogger_OAuthException( 
     722        "Expired timestamp, yours $timestamp, ours $now" 
     723      ); 
     724    } 
     725  } 
     726 
     727  /** 
     728   * check that the nonce is not repeated 
     729   */ 
     730  private function check_nonce($consumer, $token, $nonce, $timestamp) { 
     731    if( ! $nonce ) 
     732      throw new Blogger_OAuthException( 
     733        'Missing nonce parameter. The parameter is required' 
     734      ); 
     735 
     736    // verify that the nonce is uniqueish 
     737    $found = $this->data_store->lookup_nonce( 
     738      $consumer, 
     739      $token, 
     740      $nonce, 
     741      $timestamp 
     742    ); 
     743    if ($found) { 
     744      throw new Blogger_OAuthException("Nonce already used: $nonce"); 
     745    } 
     746  } 
     747 
     748} 
     749 
     750class Blogger_OAuthDataStore { 
     751  function lookup_consumer($consumer_key) { 
     752    // implement me 
     753  } 
     754 
     755  function lookup_token($consumer, $token_type, $token) { 
     756    // implement me 
     757  } 
     758 
     759  function lookup_nonce($consumer, $token, $nonce, $timestamp) { 
     760    // implement me 
     761  } 
     762 
     763  function new_request_token($consumer, $callback = null) { 
     764    // return a new token attached to this consumer 
     765  } 
     766 
     767  function new_access_token($token, $consumer, $verifier = null) { 
     768    // return a new access token attached to this consumer 
     769    // for the user associated with this token if the request token 
     770    // is authorized 
     771    // should also invalidate the request token 
     772  } 
     773 
     774} 
     775 
     776class Blogger_OAuthUtil { 
     777  public static function urlencode_rfc3986($input) { 
     778  if (is_array($input)) { 
     779    return array_map(array('Blogger_OAuthUtil', 'urlencode_rfc3986'), $input); 
     780  } else if (is_scalar($input)) { 
     781    return str_replace( 
     782      '+', 
     783      ' ', 
     784      str_replace('%7E', '~', rawurlencode($input)) 
     785    ); 
     786  } else { 
     787    return ''; 
     788  } 
     789} 
     790 
     791 
     792  // This decode function isn't taking into consideration the above 
     793  // modifications to the encoding process. However, this method doesn't 
     794  // seem to be used anywhere so leaving it as is. 
     795  public static function urldecode_rfc3986($string) { 
     796    return urldecode($string); 
     797  } 
     798 
     799  // Utility function for turning the Authorization: header into 
     800  // parameters, has to do some unescaping 
     801  // Can filter out any non-oauth parameters if needed (default behaviour) 
     802  // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement. 
     803  //                  see http://code.google.com/p/oauth/issues/detail?id=163 
     804  public static function split_header($header, $only_allow_oauth_parameters = true) { 
     805    $params = array(); 
     806    if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) { 
     807      foreach ($matches[1] as $i => $h) { 
     808        $params[$h] = Blogger_OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]); 
     809      } 
     810      if (isset($params['realm'])) { 
     811        unset($params['realm']); 
     812      } 
     813    } 
     814    return $params; 
     815  } 
     816 
     817  // helper to try to sort out headers for people who aren't running apache 
     818  public static function get_headers() { 
     819    if (function_exists('apache_request_headers')) { 
     820      // we need this to get the actual Authorization: header 
     821      // because apache tends to tell us it doesn't exist 
     822      $headers = apache_request_headers(); 
     823 
     824      // sanitize the output of apache_request_headers because 
     825      // we always want the keys to be Cased-Like-This and arh() 
     826      // returns the headers in the same case as they are in the 
     827      // request 
     828      $out = array(); 
     829      foreach ($headers AS $key => $value) { 
     830        $key = str_replace( 
     831            " ", 
     832            "-", 
     833            ucwords(strtolower(str_replace("-", " ", $key))) 
     834          ); 
     835        $out[$key] = $value; 
     836      } 
     837    } else { 
     838      // otherwise we don't have apache and are just going to have to hope 
     839      // that $_SERVER actually contains what we need 
     840      $out = array(); 
     841      if( isset($_SERVER['CONTENT_TYPE']) ) 
     842        $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; 
     843      if( isset($_ENV['CONTENT_TYPE']) ) 
     844        $out['Content-Type'] = $_ENV['CONTENT_TYPE']; 
     845 
     846      foreach ($_SERVER as $key => $value) { 
     847        if (substr($key, 0, 5) == "HTTP_") { 
     848          // this is chaos, basically it is just there to capitalize the first 
     849          // letter of every word that is not an initial HTTP and strip HTTP 
     850          // code from przemek 
     851          $key = str_replace( 
     852            " ", 
     853            "-", 
     854            ucwords(strtolower(str_replace("_", " ", substr($key, 5)))) 
     855          ); 
     856          $out[$key] = $value; 
     857        } 
     858      } 
     859    } 
     860    return $out; 
     861  } 
     862 
     863  // This function takes a input like a=b&a=c&d=e and returns the parsed 
     864  // parameters like this 
     865  // array('a' => array('b','c'), 'd' => 'e') 
     866  public static function parse_parameters( $input ) { 
     867    if (!isset($input) || !$input) return array(); 
     868 
     869    $pairs = explode('&', $input); 
     870 
     871    $parsed_parameters = array(); 
     872    foreach ($pairs as $pair) { 
     873      $split = explode('=', $pair, 2); 
     874      $parameter = Blogger_OAuthUtil::urldecode_rfc3986($split[0]); 
     875      $value = isset($split[1]) ? Blogger_OAuthUtil::urldecode_rfc3986($split[1]) : ''; 
     876 
     877      if (isset($parsed_parameters[$parameter])) { 
     878        // We have already recieved parameter(s) with this name, so add to the list 
     879        // of parameters with this name 
     880 
     881        if (is_scalar($parsed_parameters[$parameter])) { 
     882          // This is the first duplicate, so transform scalar (string) into an array 
     883          // so we can add the duplicates 
     884          $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]); 
     885        } 
     886 
     887        $parsed_parameters[$parameter][] = $value; 
     888      } else { 
     889        $parsed_parameters[$parameter] = $value; 
     890      } 
     891    } 
     892    return $parsed_parameters; 
     893  } 
     894 
     895  public static function build_http_query($params) { 
     896    if (!$params) return ''; 
     897 
     898    // Urlencode both keys and values 
     899    $keys = Blogger_OAuthUtil::urlencode_rfc3986(array_keys($params)); 
     900    $values = Blogger_OAuthUtil::urlencode_rfc3986(array_values($params)); 
     901    $params = array_combine($keys, $values); 
     902 
     903    // Parameters are sorted by name, using lexicographical byte value ordering. 
     904    // Ref: Spec: 9.1.1 (1) 
     905    uksort($params, 'strcmp'); 
     906 
     907    $pairs = array(); 
     908    foreach ($params as $parameter => $value) { 
     909      if (is_array($value)) { 
     910        // If two or more parameters share the same name, they are sorted by their value 
     911        // Ref: Spec: 9.1.1 (1) 
     912        // June 12th, 2010 - changed to sort because of issue 164 by hidetaka 
     913        sort($value, SORT_STRING); 
     914        foreach ($value as $duplicate_value) { 
     915          $pairs[] = $parameter . '=' . $duplicate_value; 
     916        } 
     917      } else { 
     918        $pairs[] = $parameter . '=' . $value; 
     919      } 
     920    } 
     921    // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) 
     922    // Each name-value pair is separated by an '&' character (ASCII code 38) 
     923    return implode('&', $pairs); 
     924  } 
     925} 
     926 
  • readme.txt

     
    1 === Blogger Importer === 
    2 Contributors: wordpressdotorg 
     1=== Plugin Name === 
     2Contributors: wordpressdotorg, otto42, workshopshed, SergeyBiryukov, rmccue 
    33Donate link:  
    44Tags: importer, blogger 
    55Requires at least: 3.0 
    66Tested up to: 3.2 
    7 Stable tag: 0.4 
     7Stable tag: 0.6 
     8License: GPLv2 or later 
    89 
    9 Import posts, comments, and users from a Blogger blog. 
     10Imports posts, comments and categories (blogger tags) from a Blogger blog then migrates authors to Wordpress users. 
    1011 
    1112== Description == 
    1213 
    13 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. 
     14The Blogger Importer imports your blog data from a Blogger site into a WordPress.org installation. 
    1415 
     16= Items imported = 
     17 
     18* Categories 
     19* Posts (published, scheduled and draft) 
     20* Comments (not spam) 
     21 
     22= Items not imported = 
     23 
     24* Pages 
     25* Images (the images will appear in your new blog but will link to the old blogspot or picassa web locations) 
     26 
    1527== Installation == 
    1628 
    17291. Upload the `blogger-importer` folder to the `/wp-content/plugins/` directory 
    18301. Activate the plugin through the 'Plugins' menu in WordPress 
    19 1. Go to the Tools -> Import screen, Click on Blogger 
    2031 
     32= How to use = 
     33 
     341. Blogger Importer is available from the WordPress Tools->Import screen. 
     351. Press Authorise 
     361. If you are not already logged into Google you will be asked to login 
     371. You will be asked to grant Wordpress access to your Blogger information, to continue press Grant Access 
     381. You will be presented with a list of all your blogs 
     391. Select the appropriate blog and press the import button 
     401. Wait whilst the posts and comments are imported 
     411. Press the Set Authors button 
     421. Select the appropriate mapping for the authors 
     431. Review categories, posts and comments 
     44 
     45You can now remove the importer plugin if you no longer need to use it. 
     46 
    2147== Frequently Asked Questions == 
    2248 
     49= How do I re-import? = 
     50 
     51Press 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. 
     52 
     53= How do I know which posts were imported? =  
     54 
     55Each 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 
     56 
     57* blogger_author 
     58* blogger_blog 
     59* blogger_permalink 
     60 
     61= Why does it keep stopping? =  
     62 
     63The 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. 
     64 
     65= After importing there are a lot of categories = 
     66 
     67Blogger 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 
     68 
     69= What about pages? = 
     70 
     71This importer does not handle blogger pages, you will need to manually transfer them. 
     72 
     73= What about images? = 
     74 
     75The 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. 
     76 
     77= Are the permalinks the same? = 
     78 
     79No, 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. 
     80 
     81= What about future posts? = 
     82 
     83The 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. 
     84 
     85= My posts and comments moved across but some things are stripped out = 
     86 
     87The 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. 
     88 
    2389== Screenshots == 
    2490 
     91== Reference == 
     92 
     93* https://developers.google.com/blogger/docs/1.0/developers_guide_php 
     94* https://developers.google.com/gdata/articles/oauth 
     95 
     96== Other changes to review == 
     97 
     98Review the santisation processes, are these being done twice? 
     99 
     100http://core.trac.wordpress.org/attachment/ticket/15737/blogger-importer.patch 
     101http://core.trac.wordpress.org/ticket/15737 
     102 
     103 
     104 
     105http://core.trac.wordpress.org/ticket/7652 
     106http://core.trac.wordpress.org/attachment/ticket/7652/7652-blogger.diff 
     107http://core.trac.wordpress.org/attachment/ticket/7652/7652-separate.diff 
     108 
     109Move the Javascript into a separate file, pass the parameters and localised strings using wp_localize_script 
     110 
     111Inconsistent UI (sometimes you get a set of tools graphic, sometimes not) 
     112 
     113Handle geotags See http://codex.wordpress.org/Geodata 
     114 
     115 
     116 
    25117== Changelog == 
    26118 
     119= 0.6 = 
     120* Merged in fix by SergeyBiryukov http://core.trac.wordpress.org/ticket/16012 
     121* Merged in rmccue change to get_total_results to also use SimplePie from http://core.trac.wordpress.org/attachment/ticket/7652/7652-blogger.diff 
     122* 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 
     123* Moved SimplePie functions in  new class WP_SimplePie_Blog_Item incorporating get_draft_status and get_updated and convert date 
     124* Andy from Workshopshed tested comments from source blog GMT-8, destination London (currently GMT-1), comment dates transferred correctly. 
     125* Andy from Workshopshed Fixed typo in oauth_get 
     126 
     127= 0.5 = 
     128* 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 
     129  this also fixes http://core.trac.wordpress.org/ticket/15560  
     130* Change by Otto42 to use OAuth rather than AuthSub authentication, should make authentication more reliable 
     131* Fix by Andy from Workshopshed to load comments correctly 
     132* 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 
     133* Fix by Andy from Workshopshed error about incorrect enqueuing of scripts also changed styles to work the same 
     134* Change by Andy from Workshopshed testing in debug mode and wrapped ajax return into a function to suppress debug messages 
     135* Fix by Andy from Workshopshed notices for undefined variables. 
     136* Change by Andy from Workshopshed Added tooltip to results table to show numbers of posts and comments skipped (duplicates / missing key) 
     137* 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. 
     138 
    27139= 0.4 = 
    28140* Fix for tracking images being added by Blogger to non-authenticated feeds http://core.trac.wordpress.org/ticket/17623 
    29141 
     
    35147 
    36148== Upgrade Notice == 
    37149 
    38 = 0.4 = 
    39 * Fix for tracking images being added by Blogger to non-authenticated feeds http://core.trac.wordpress.org/ticket/17623 
     150= 0.6 = 
    40151 
    41 = 0.3 = 
    42 * Bugfix for 403 Invalid AuthSub Token 
     152Merged in fixes found in Trac 
    43153 
     154= 0.5 = 
     155 
     156This version is a significant re-write based on previous versions.  
     157