Changeset 4896

Show
Ignore:
Timestamp:
02/20/07 07:46:40 (2 years ago)
Author:
ryan
Message:

Update blogger importer.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/wp-admin/import/blogger.php

    r4679 r4896  
    11<?php 
    22 
     3define( 'MAX_RESULTS',        50 ); // How many records per GData query 
     4define( 'MAX_EXECUTION_TIME', 20 ); // How many seconds to let the script run 
     5define( 'STATUS_INTERVAL',     3 ); // How many seconds between status bar updates 
     6 
    37class Blogger_Import { 
    48 
    5     var $lump_authors = false; 
    6     var $import = array(); 
    7  
    8     // Shows the welcome screen and the magic iframe. 
     9    // Shows the welcome screen and the magic auth link. 
    910    function greet() { 
    10         $title = __('Import Old Blogger'); 
    11         $welcome = __('Howdy! This importer allows you to import posts and comments from your Old Blogger account into your WordPress blog.'); 
    12         $noiframes = __('This feature requires iframe support.'); 
    13         $warning = js_escape(__('This will delete everything saved by the Blogger importer except your posts and comments. Are you sure you want to do this?')); 
    14         $reset = __('Reset this importer'); 
    15         $incompat = __('Your web server is not properly configured to use this importer. Please enable the CURL extension for PHP and then reload this page.'); 
    16  
    17         echo "<div class='wrap'><h2>$title</h2><p>$welcome</p>"; 
    18         echo "<p>" . __('Please note that this importer <em>does not work with new Blogger (using your Google account)</em>.') . "</p>"; 
    19         if ( function_exists('curl_init') ) 
    20             echo "<iframe src='admin.php?import=blogger&amp;noheader=true' height='350px' width = '99%'>$noiframes</iframe><p><a href='admin.php?import=blogger&amp;restart=true&amp;noheader=true' onclick='return confirm(\"$warning\")'>$reset</a></p>"; 
     11        $next_url = get_option('siteurl') . '/wp-admin/index.php?import=blogger&noheader=true'; 
     12        $auth_url = "https://www.google.com/accounts/AuthSubRequest"; 
     13        $title = __('Import Blogger'); 
     14        $welcome = __('Howdy! This importer allows you to import posts and comments from your Blogger account into your WordPress blog.'); 
     15        $prereqs = __('To use this importer, you must have a Google account, an upgraded (New, was Beta) blog, and it must be on blogspot or a custom domain (not FTP).'); 
     16        $stepone = __('The first thing you need to do is tell Blogger to let WordPress access your account. You will be sent back here after providing authorization.'); 
     17        $auth = __('Authorize'); 
     18 
     19        echo " 
     20        <div class='wrap'><h2>$title</h2><p>$welcome</p><p>$prereqs</p><p>$stepone</p> 
     21            <form action='$auth_url' method='get'> 
     22                <p class='submit' style='text-align:left;'> 
     23                    <input type='submit' value='$auth' /> 
     24                    <input type='hidden' name='scope' value='http://www.blogger.com/feeds/' /> 
     25                    <input type='hidden' name='session' value='1' /> 
     26                    <input type='hidden' name='secure' value='0' /> 
     27                    <input type='hidden' name='next' value='$next_url' /> 
     28                </p> 
     29            </form> 
     30        </div>\n"; 
     31    } 
     32 
     33    function uh_oh($title, $message, $info) { 
     34        echo "<div class='wrap'><h2>$title</h2><p>$message</p><pre>$info</pre></div>"; 
     35    } 
     36 
     37    function auth() { 
     38        // We have a single-use token that must be upgraded to a session token. 
     39        $token = preg_replace( '/[^-_0-9a-zA-Z]/', '', $_GET['token'] ); 
     40        $headers = array( 
     41            "GET /accounts/AuthSubSessionToken HTTP/1.0", 
     42            "Authorization: AuthSub token=\"$token\"" 
     43        ); 
     44        $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
     45        $sock = $this->_get_auth_sock( ); 
     46        if ( ! $sock ) return false; 
     47        $response = $this->_txrx( $sock, $request ); 
     48        preg_match( '/token=([-_0-9a-z]+)/i', $response, $matches ); 
     49        if ( empty( $matches[1] ) ) { 
     50            $this->uh_oh( 
     51                __( 'Authorization failed' ), 
     52                __( 'Something went wrong. If the problem persists, send this info to support:' ), 
     53                htmlspecialchars($response) 
     54            ); 
     55            return false; 
     56        } 
     57        $this->token = $matches[1]; 
     58         
     59        wp_redirect( remove_query_arg( array( 'token', 'noheader' ) ) ); 
     60    } 
     61 
     62    function get_token_info() { 
     63        $headers = array( 
     64            "GET /accounts/AuthSubTokenInfo  HTTP/1.0", 
     65            "Authorization: AuthSub token=\"$this->token\"" 
     66        ); 
     67        $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
     68        $sock = $this->_get_auth_sock( ); 
     69        if ( ! $sock ) return; 
     70        $response = $this->_txrx( $sock, $request ); 
     71        return $this->parse_response($response); 
     72    } 
     73 
     74    function token_is_valid() { 
     75        $info = $this->get_token_info(); 
     76 
     77        if ( $info['code'] == 200 ) 
     78            return true; 
     79 
     80        return false; 
     81    } 
     82 
     83    function show_blogs($iter = 0) { 
     84        if ( empty($this->blogs) ) { 
     85            $headers = array( 
     86                "GET /feeds/default/blogs HTTP/1.0", 
     87                "Host: www.blogger.com", 
     88                "Authorization: AuthSub token=\"$this->token\"" 
     89            ); 
     90            $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
     91            $sock = $this->_get_blogger_sock( ); 
     92            if ( ! $sock ) return; 
     93            $response = $this->_txrx( $sock, $request ); 
     94 
     95            // Quick and dirty XML mining. 
     96            list( $headers, $xml ) = explode( "\r\n\r\n", $response ); 
     97            $p = xml_parser_create(); 
     98            xml_parse_into_struct($p, $xml, $vals, $index); 
     99            xml_parser_free($p); 
     100 
     101            $this->title = $vals[$index['TITLE'][0]]['value']; 
     102 
     103            // Give it a few retries... this step often flakes out the first time. 
     104            if ( empty( $index['ENTRY'] ) ) { 
     105                if ( $iter < 3 ) { 
     106                    return $this->show_blogs($iter + 1); 
     107                } else { 
     108                    $this->uh_oh( 
     109                        __('Trouble signing in'), 
     110                        __('We were not able to gain access to your account. Try starting over.'), 
     111                        '' 
     112                    ); 
     113                    return false; 
     114                } 
     115            } 
     116 
     117            foreach ( $index['ENTRY'] as $i ) { 
     118                $blog = array(); 
     119                while ( ( $tag = $vals[$i] ) && ! ( $tag['tag'] == 'ENTRY' && $tag['type'] == 'close' ) ) { 
     120                    if ( $tag['tag'] == 'TITLE' ) { 
     121                        $blog['title'] = $tag['value']; 
     122                    } elseif ( $tag['tag'] == 'SUMMARY' ) { 
     123                        $blog['summary'] == $tag['value']; 
     124                    } elseif ( $tag['tag'] == 'LINK' ) { 
     125                        if ( $tag['attributes']['REL'] == 'alternate' && $tag['attributes']['TYPE'] == 'text/html' ) { 
     126                            $parts = parse_url( $tag['attributes']['HREF'] ); 
     127                            $blog['host'] = $parts['host']; 
     128                        } elseif ( $tag['attributes']['REL'] == 'edit' ) 
     129                            $blog['gateway'] = $tag['attributes']['HREF']; 
     130                    } 
     131                    ++$i; 
     132                } 
     133                if ( ! empty ( $blog ) ) { 
     134                    $blog['total_posts'] = $this->get_total_results('posts', $blog['host']); 
     135                    $blog['total_comments'] = $this->get_total_results('comments', $blog['host']); 
     136                    $blog['mode'] = 'init'; 
     137                    $this->blogs[] = $blog; 
     138                } 
     139            } 
     140 
     141            if ( empty( $this->blogs ) ) { 
     142                $this->uh_oh( 
     143                    __('No blogs found'), 
     144                    __('We were able to log in but there were no blogs. Try a different account next time.'), 
     145                    '' 
     146                ); 
     147                return false; 
     148            } 
     149        } 
     150//echo '<pre>'.print_r($this,1).'</pre>'; 
     151        $start    = js_escape( __('Import') ); 
     152        $continue = js_escape( __('Continue') ); 
     153        $stop     = js_escape( __('Importing...') ); 
     154        $authors  = js_escape( __('Set Authors') ); 
     155        $loadauth = js_escape( __('Preparing author mapping form...') ); 
     156        $authhead = js_escape( __('Final Step: Author Mapping') ); 
     157        $nothing  = js_escape( __('Nothing was imported. Had you already imported this blog?') ); 
     158        $title    = __('Blogger Blogs'); 
     159        $name     = __('Blog Name'); 
     160        $url      = __('Blog URL'); 
     161        $action   = __('The Magic Button'); 
     162        $posts    = __('Posts'); 
     163        $comments = __('Comments'); 
     164        $noscript = __('This feature requires Javascript but it seems to be disabled. Please enable Javascript and then reload this page. Don\'t worry, you can turn it back off when you\'re done.'); 
     165 
     166        $interval = STATUS_INTERVAL * 1000; 
     167 
     168        foreach ( $this->blogs as $i => $blog ) { 
     169            if ( $blog['mode'] == 'init' ) 
     170                $value = $start; 
     171            elseif ( $blog['mode'] == 'posts' || $blog['mode'] == 'comments' ) 
     172                $value = $continue; 
     173            else 
     174                $value = $authors; 
     175            $blogtitle = js_escape( $blog['title'] ); 
     176            $pdone = isset($blog['posts_done']) ? (int) $blog['posts_done'] : 0; 
     177            $cdone = isset($blog['comments_done']) ? (int) $blog['comments_done'] : 0; 
     178            $init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}'," . $this->get_js_status($i) . ');'; 
     179            $pstat = "<div class='ind' id='pind$i'>&nbsp;</div><div id='pstat$i' class='stat'>$pdone/{$blog['total_posts']}</div>"; 
     180            $cstat = "<div class='ind' id='cind$i'>&nbsp;</div><div id='cstat$i' class='stat'>$cdone/{$blog['total_comments']}</div>"; 
     181            $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' id='submit$i' value='$value' /><input type='hidden' name='blog' value='$i' /></td></tr>\n"; 
     182        } 
     183 
     184        echo "<div class='wrap'><h2>$title</h2><noscript>$noscript</noscript><table cellpadding='5px'><thead><td>$name</td><td>$url</td><td>$posts</td><td>$comments</td><td>$action</td></thead>\n$rows</table></form></div>"; 
     185        echo " 
     186        <script type='text/javascript'> 
     187            var strings = {cont:'$continue',stop:'$stop',stopping:'$stopping',authors:'$authors',nothing:'$nothing'}; 
     188            var blogs = {}; 
     189            function blog(i, title, mode, status){ 
     190                this.blog   = i; 
     191                this.mode   = mode; 
     192                this.title  = title; 
     193                this.status = status; 
     194                this.button = document.getElementById('submit'+this.blog); 
     195            }; 
     196            blog.prototype = { 
     197                start: function() { 
     198                    this.cont = true; 
     199                    this.kick(); 
     200                    this.check(); 
     201                }, 
     202                kick: function() { 
     203                    ++this.kicks; 
     204                    var i = this.blog; 
     205                    jQuery.post('admin.php?import=blogger&noheader=true',{blog:this.blog},function(text,result){blogs[i].kickd(text,result)}); 
     206                }, 
     207                check: function() { 
     208                    ++this.checks; 
     209                    var i = this.blog; 
     210                    jQuery.post('admin.php?import=blogger&noheader=true&status=true',{blog:this.blog},function(text,result){blogs[i].checkd(text,result)}); 
     211                }, 
     212                kickd: function(text, result) { 
     213                    if ( result == 'error' ) { 
     214                        // TODO: exception handling 
     215                        if ( this.cont ) 
     216                            setTimeout('blogs['+this.blog+'].kick()', 1000); 
     217                    } else { 
     218                        if ( text == 'done' ) { 
     219                            this.stop(); 
     220                            this.done(); 
     221                        } else if ( text == 'nothing' ) { 
     222                            this.stop(); 
     223                            this.nothing(); 
     224                        } else if ( text == 'continue' ) { 
     225                            this.kick(); 
     226                        } else if ( this.mode = 'stopped' ) 
     227                            jQuery(this.button).attr('value', strings.cont); 
     228                    } 
     229                    --this.kicks; 
     230                }, 
     231                checkd: function(text, result) { 
     232                    if ( result == 'error' ) { 
     233                        // TODO: exception handling 
     234                    } else { 
     235                        eval('this.status='+text); 
     236                        jQuery('#pstat'+this.blog).empty().append(this.status.p1+'/'+this.status.p2); 
     237                        jQuery('#cstat'+this.blog).empty().append(this.status.c1+'/'+this.status.c2); 
     238                        this.update(); 
     239                        if ( this.cont || this.kicks > 0 ) 
     240                            setTimeout('blogs['+this.blog+'].check()', $interval); 
     241                    } 
     242                    --this.checks; 
     243                }, 
     244                update: function() { 
     245                    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'); 
     246                    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'); 
     247                }, 
     248                stop: function() { 
     249                    this.cont = false; 
     250                }, 
     251                done: function() { 
     252                    this.mode = 'authors'; 
     253                    jQuery(this.button).attr('value', strings.authors); 
     254                }, 
     255                nothing: function() { 
     256                    this.mode = 'nothing'; 
     257                    jQuery(this.button).remove(); 
     258                    alert(strings.nothing); 
     259                }, 
     260                getauthors: function() { 
     261                    if ( jQuery('div.wrap').length > 1 ) 
     262                        jQuery('div.wrap').gt(0).remove(); 
     263                    jQuery('div.wrap').empty().append('<h2>$authhead</h2><h3>' + this.title + '</h3>'); 
     264                    jQuery('div.wrap').append('<p id=\"auth\">$loadauth</p>'); 
     265                    jQuery('p#auth').load('index.php?import=blogger&noheader=true&authors=1',{blog:this.blog}); 
     266                }, 
     267                init: function() { 
     268                    this.update(); 
     269                    var i = this.blog; 
     270                    jQuery(this.button).bind('click', function(){return blogs[i].click();}); 
     271                    this.kicks = 0; 
     272                    this.checks = 0; 
     273                }, 
     274                click: function() { 
     275                    if ( this.mode == 'init' || this.mode == 'stopped' || this.mode == 'posts' || this.mode == 'comments' ) { 
     276                        this.mode = 'started'; 
     277                        this.start(); 
     278                        jQuery(this.button).attr('value', strings.stop); 
     279                    } else if ( this.mode == 'started' ) { 
     280                        return false; // let it run... 
     281                        this.mode = 'stopped'; 
     282                        this.stop(); 
     283                        if ( this.checks > 0 || this.kicks > 0 ) { 
     284                            this.mode = 'stopping'; 
     285                            jQuery(this.button).attr('value', strings.stopping); 
     286                        } else { 
     287                            jQuery(this.button).attr('value', strings.cont); 
     288                        } 
     289                    } else if ( this.mode == 'authors' ) { 
     290                        document.location = 'index.php?import=blogger&authors=1&blog='+this.blog; 
     291                        //this.mode = 'authors2'; 
     292                        //this.getauthors(); 
     293                    } 
     294                    return false; 
     295                } 
     296            }; 
     297            $init 
     298            jQuery.each(blogs, function(i, me){me.init();}); 
     299        </script>\n"; 
     300    } 
     301 
     302    // Handy function for stopping the script after a number of seconds. 
     303    function have_time() { 
     304        global $importer_started; 
     305        if ( time() - $importer_started > MAX_EXECUTION_TIME ) 
     306            die('continue'); 
     307        return true; 
     308    } 
     309 
     310    function get_total_results($type, $host) { 
     311        $headers = array( 
     312            "GET /feeds/$type/default?max-results=1&start-index=2 HTTP/1.0", 
     313            "Host: $host", 
     314            "Authorization: AuthSub token=\"$this->token\"" 
     315        ); 
     316        $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
     317        $sock = $this->_get_blogger_sock( $host ); 
     318        if ( ! $sock ) return; 
     319        $response = $this->_txrx( $sock, $request ); 
     320        $response = $this->parse_response( $response ); 
     321        $parser = xml_parser_create(); 
     322        xml_parse_into_struct($parser, $response['body'], $struct, $index); 
     323        xml_parser_free($parser); 
     324        $total_results = $struct[$index['OPENSEARCH:TOTALRESULTS'][0]]['value']; 
     325        return (int) $total_results; 
     326    } 
     327 
     328    function import_blog($blogID) { 
     329        global $importing_blog; 
     330        $importing_blog = $blogID; 
     331 
     332        if ( isset($_GET['authors']) ) 
     333            return print($this->get_author_form()); 
     334 
     335        header('Content-Type: text/plain'); 
     336 
     337        if ( isset($_GET['status']) ) 
     338            die($this->get_js_status()); 
     339 
     340        if ( isset($_GET['saveauthors']) ) 
     341            die($this->save_authors()); 
     342 
     343        $blog = $this->blogs[$blogID]; 
     344        $total_results = $this->get_total_results('posts', $blog['host']); 
     345        $this->blogs[$importing_blog]['total_posts'] = $total_results; 
     346 
     347        $start_index = $total_results - MAX_RESULTS + 1; 
     348 
     349        if ( isset( $this->blogs[$importing_blog]['posts_start_index'] ) ) 
     350            $start_index = (int) $this->blogs[$importing_blog]['posts_start_index']; 
     351        elseif ( $total_results > MAX_RESULTS ) 
     352            $start_index = $total_results - MAX_RESULTS + 1; 
    21353        else 
    22             echo "<p>$incompat</p>"; 
    23         echo "</div>\n"; 
    24     } 
    25  
    26     // Deletes saved data and redirect. 
     354            $start_index = 1; 
     355 
     356        // This will be positive until we have finished importing posts 
     357        if ( $start_index > 0 ) { 
     358            // Grab all the posts 
     359            $this->blogs[$importing_blog]['mode'] = 'posts'; 
     360            $query = "start-index=$start_index&max-results=" . MAX_RESULTS; 
     361            do { 
     362                $index = $struct = $entries = array(); 
     363                $headers = array( 
     364                    "GET /feeds/posts/default?$query HTTP/1.0", 
     365                    "Host: {$blog['host']}", 
     366                    "Authorization: AuthSub token=\"$this->token\"" 
     367                ); 
     368                $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
     369                $sock = $this->_get_blogger_sock( $blog['host'] ); 
     370                if ( ! $sock ) return; // TODO: Error handling 
     371                $response = $this->_txrx( $sock, $request ); 
     372     
     373                $response = $this->parse_response( $response ); 
     374     
     375                // Extract the entries and send for insertion 
     376                preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches ); 
     377                if ( count( $matches[0] ) ) { 
     378                    $entries = array_reverse($matches[0]); 
     379                    foreach ( $entries as $entry ) { 
     380                        $entry = "<feed>$entry</feed>"; 
     381                        $AtomParser = new AtomParser(); 
     382                        $AtomParser->parse( $entry ); 
     383                        $this->import_post($AtomParser->entry); 
     384                        unset($AtomParser); 
     385                    } 
     386                } else break; 
     387 
     388                // Get the 'previous' query string which we'll use on the next iteration 
     389                $query = ''; 
     390                $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches); 
     391                if ( count( $matches[1] ) ) 
     392                    foreach ( $matches[1] as $match ) 
     393                        if ( preg_match('/rel=.previous./', $match) ) 
     394                            $query = html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match) ); 
     395 
     396                if ( $query ) { 
     397                    parse_str($query, $q); 
     398                    $this->blogs[$importing_blog]['posts_start_index'] = (int) $q['start-index']; 
     399                } else 
     400                    $this->blogs[$importing_blog]['posts_start_index'] = 0; 
     401                $this->save_vars(); 
     402            } while ( !empty( $query ) && $this->have_time() ); 
     403        } 
     404 
     405        $total_results = $this->get_total_results( 'comments', $blog['host'] ); 
     406        $this->blogs[$importing_blog]['total_comments'] = $total_results; 
     407 
     408        if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) ) 
     409            $start_index = (int) $this->blogs[$importing_blog]['comments_start_index']; 
     410        elseif ( $total_results > MAX_RESULTS ) 
     411            $start_index = $total_results - MAX_RESULTS + 1; 
     412        else 
     413            $start_index = 1; 
     414 
     415        if ( $start_index > 0 ) { 
     416            // Grab all the comments 
     417            $this->blogs[$importing_blog]['mode'] = 'comments'; 
     418            $query = "start-index=$start_index&max-results=" . MAX_RESULTS; 
     419            do { 
     420                $index = $struct = $entries = array(); 
     421                $headers = array( 
     422                    "GET /feeds/comments/default?$query HTTP/1.0", 
     423                    "Host: {$blog['host']}", 
     424                    "Authorization: AuthSub token=\"$this->token\"" 
     425                ); 
     426                $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
     427                $sock = $this->_get_blogger_sock( $blog['host'] ); 
     428                if ( ! $sock ) return; // TODO: Error handling 
     429                $response = $this->_txrx( $sock, $request ); 
     430 
     431                $response = $this->parse_response( $response ); 
     432 
     433                // Extract the comments and send for insertion 
     434                preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches ); 
     435                if ( count( $matches[0] ) ) { 
     436                    $entries = array_reverse( $matches[0] ); 
     437                    foreach ( $entries as $entry ) { 
     438                        $entry = "<feed>$entry</feed>"; 
     439                        $AtomParser = new AtomParser(); 
     440                        $AtomParser->parse( $entry );    
     441                        $this->import_comment($AtomParser->entry); 
     442                        unset($AtomParser); 
     443                    } 
     444                } 
     445 
     446                // Get the 'previous' query string which we'll use on the next iteration 
     447                $query = ''; 
     448                $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches); 
     449                if ( count( $matches[1] ) ) 
     450                    foreach ( $matches[1] as $match ) 
     451                        if ( preg_match('/rel=.previous./', $match) ) 
     452                            $query = html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match) ); 
     453 
     454                parse_str($query, $q); 
     455 
     456                $this->blogs[$importing_blog]['comments_start_index'] = (int) $q['start-index']; 
     457                $this->save_vars(); 
     458            } while ( !empty( $query ) && $this->have_time() ); 
     459        } 
     460        $this->blogs[$importing_blog]['mode'] = 'authors'; 
     461        $this->save_vars(); 
     462        if ( !$this->blogs[$importing_blog]['posts_done'] && !$this->blogs[$importing_blog]['comments_done'] ) 
     463            die('nothing'); 
     464        do_action('import_done', 'blogger'); 
     465        die('done'); 
     466    } 
     467 
     468    function convert_date( $date ) { 
     469        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); 
     470        $offset = iso8601_timezone_to_offset( $date_bits[7] ); 
     471        $timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]); 
     472        $timestamp -= $offset; // Convert from Blogger local time to GMT 
     473        $timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time 
     474        return gmdate('Y-m-d H:i:s', $timestamp); 
     475    } 
     476 
     477    function no_apos( $string ) { 
     478        return str_replace( '&apos;', "'", $string); 
     479    } 
     480 
     481    function min_whitespace( $string ) { 
     482        return preg_replace( '|\s+|', ' ', $string ); 
     483    } 
     484 
     485    function import_post( $entry ) { 
     486        global $wpdb, $importing_blog; 
     487 
     488        // The old permalink is all Blogger gives us to link comments to their posts. 
     489        if ( isset( $entry->draft ) ) 
     490            $rel = 'self'; 
     491        else 
     492            $rel = 'alternate'; 
     493        foreach ( $entry->links as $link ) { 
     494            if ( $link['rel'] == $rel ) { 
     495                $parts = parse_url( $link['href'] ); 
     496                $entry->old_permalink = $parts['path']; 
     497                break; 
     498            } 
     499        } 
     500 
     501        $post_date    = $this->convert_date( $entry->published ); 
     502        $post_content = trim( addslashes( $this->no_apos( html_entity_decode( $entry->content ) ) ) ); 
     503        $post_title   = trim( addslashes( $this->no_apos( $this->min_whitespace( $entry->title ) ) ) ); 
     504        $post_status  = isset( $entry->draft ) ? 'draft' : 'publish'; 
     505 
     506        // Clean up content 
     507        $post_content = preg_replace('|<(/?[A-Z]+)|e', "'<' . strtolower('$1')", $post_content); 
     508        $post_content = str_replace('<br>', '<br />', $post_content); 
     509        $post_content = str_replace('<hr>', '<hr />', $post_content); 
     510 
     511        // Checks for duplicates 
     512        if ( 
     513            isset( $this->blogs[$importing_blog]['posts'][$entry->old_permalink] ) || 
     514            post_exists( $post_title, $post_content, $post_date ) 
     515        ) { 
     516            ++$this->blogs[$importing_blog]['posts_skipped']; 
     517        } else { 
     518            $post = compact('post_date', 'post_content', 'post_title', 'post_status'); 
     519 
     520            $post_id = wp_insert_post($post); 
     521 
     522            wp_create_categories( array_map( 'addslashes', $entry->categories ), $post_id ); 
     523 
     524            $author = $this->no_apos( strip_tags( $entry->author ) ); 
     525 
     526            add_post_meta( $post_id, 'blogger_blog', $this->blogs[$importing_blog]['host'], true ); 
     527            add_post_meta( $post_id, 'blogger_author', $author, true ); 
     528            add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true ); 
     529 
     530            $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id; 
     531            ++$this->blogs[$importing_blog]['posts_done']; 
     532        } 
     533        $this->save_vars(); 
     534    } 
     535 
     536    function import_comment( $entry ) { 
     537        global $importing_blog; 
     538 
     539        // Drop the #fragment and we have the comment's old post permalink. 
     540        foreach ( $entry->links as $link ) { 
     541            if ( $link['rel'] == 'alternate' ) { 
     542                $parts = parse_url( $link['href'] ); 
     543                $entry->old_permalink = $parts['fragment']; 
     544                $entry->old_post_permalink = $parts['path']; 
     545                break; 
     546            } 
     547        } 
     548 
     549        $comment_post_ID = $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink]; 
     550        $comment_author  = addslashes( $this->no_apos( strip_tags( $entry->author ) ) ); 
     551        $comment_date    = $this->convert_date( $entry->updated ); 
     552        $comment_content = addslashes( $this->no_apos( html_entity_decode( $entry->content ) ) ); 
     553 
     554        // Clean up content 
     555        $comment_content = preg_replace('|<(/?[A-Z]+)|e', "'<' . strtolower('$1')", $comment_content); 
     556        $comment_content = str_replace('<br>', '<br />', $comment_content); 
     557        $comment_content = str_replace('<hr>', '<hr />', $comment_content); 
     558 
     559        // Checks for duplicates 
     560        if ( 
     561            isset( $this->blogs[$importing_blog]['comments'][$entry->old_permalink] ) || 
     562            comment_exists( $comment_author, $comment_date ) 
     563        ) { 
     564            ++$this->blogs[$importing_blog]['comments_skipped']; 
     565        } else { 
     566            $comment = compact('comment_post_ID', 'comment_author', 'comment_date', 'comment_content'); 
     567 
     568            $comment_id = wp_insert_comment($comment); 
     569 
     570            $this->blogs[$importing_blog]['comments'][$entry->old_permalink] = $comment_id; 
     571 
     572            ++$this->blogs[$importing_blog]['comments_done']; 
     573        } 
     574        $this->save_vars(); 
     575    } 
     576 
     577    function get_js_status($blog = false) { 
     578        global $importing_blog; 
     579        if ( $blog === false ) 
     580            $blog = $this->blogs[$importing_blog]; 
     581        else 
     582            $blog = $this->blogs[$blog]; 
     583        $p1 = isset( $blog['posts_done'] ) ? (int) $blog['posts_done'] : 0; 
     584        $p2 = isset( $blog['total_posts'] ) ? (int) $blog['total_posts'] : 0; 
     585        $c1 = isset( $blog['comments_done'] ) ? (int) $blog['comments_done'] : 0; 
     586        $c2 = isset( $blog['total_comments'] ) ? (int) $blog['total_comments'] : 0; 
     587        return "{p1:$p1,p2:$p2,c1:$c1,c2:$c2}"; 
     588    } 
     589 
     590    function get_author_form($blog = false) { 
     591        global $importing_blog, $wpdb, $current_user; 
     592        if ( $blog === false ) 
     593            $blog = & $this->blogs[$importing_blog]; 
     594        else 
     595            $blog = & $this->blogs[$blog]; 
     596 
     597        if ( !isset( $blog['authors'] ) ) { 
     598            $post_ids = array_values($blog['posts']); 
     599            $authors = (array) $wpdb->get_col("SELECT DISTINCT meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN (" . join( ',', $post_ids ) . ")"); 
     600            $blog['authors'] = array_map(null, $authors, array_fill(0, count($authors), $current_user->ID)); 
     601            $this->save_vars(); 
     602        } 
     603 
     604        $directions = __('All posts were imported with the current user as author. Use this form to move each Blogger user\'s posts to a different WordPress user. You may <a href="users.php">add users</a> and then return to this page and complete the user mapping. This form may be used as many times as you like until you activate the "Restart" function below.'); 
     605        $heading = __('Author mapping'); 
     606        $blogtitle = "{$blog['title']} ({$blog['host']})"; 
     607        $mapthis = __('Blogger username'); 
     608        $tothis = __('WordPress login'); 
     609        $submit = js_escape( __('Save Changes &raquo;') ); 
     610 
     611        foreach ( $blog['authors'] as $i => $author ) 
     612            $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>"; 
     613 
     614        return "<div class='wrap'><h2>$heading</h2><h3>$blogtitle</h3><p>$directions</p><form action='index.php?import=blogger&noheader=true&saveauthors=1' method='post'><input type='hidden' name='blog' value='$importing_blog' /><table cellpadding='5'><thead><td>$mapthis</td><td>$tothis</td></thead>$rows<tr><td></td><td class='submit'><input type='submit' class='authorsubmit' value='$submit' /></td></tr></table></form></div>"; 
     615    } 
     616 
     617    function get_user_options($current) { 
     618        global $wpdb, $importer_users; 
     619        if ( ! isset( $importer_users ) ) 
     620            $importer_users = (array) get_users_of_blog(); 
     621 
     622        foreach ( $importer_users as $user ) { 
     623            $sel = ( $user->user_id == $current ) ? " selected='selected'" : ''; 
     624            $options .= "<option value='$user->user_id'$sel>$user->display_name</option>"; 
     625        } 
     626         
     627        return $options; 
     628    } 
     629 
     630    function save_authors() { 
     631        global $importing_blog, $wpdb; 
     632        $authors = (array) $_POST['authors']; 
     633 
     634        $host = $this->blogs[$importing_blog]['host']; 
     635 
     636        // Get an array of posts => authors 
     637        $post_ids = (array) $wpdb->get_col("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'blogger_blog' AND meta_value = '$host'"); 
     638        $post_ids = join( ',', $post_ids ); 
     639        $results = (array) $wpdb->get_results("SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN ($post_ids)"); 
     640        foreach ( $results as $row ) 
     641            $authors_posts[$row->post_id] = $row->meta_value; 
     642 
     643        foreach ( $authors as $author => $user_id ) { 
     644            $user_id = (int) $user_id; 
     645 
     646            // Skip authors that haven't been changed 
     647            if ( $user_id == $this->blogs[$importing_blog]['authors'][$author][1] ) 
     648                continue; 
     649 
     650            // Get a list of the selected author's posts 
     651            $post_ids = (array) array_keys( $authors_posts, $this->blogs[$importing_blog]['authors'][$author][0] ); 
     652            $post_ids = join( ',', $post_ids); 
     653 
     654            $wpdb->query("UPDATE $wpdb->posts SET post_author = $user_id WHERE id IN ($post_ids)"); 
     655            $this->blogs[$importing_blog]['authors'][$author][1] = $user_id; 
     656        } 
     657        $this->save_vars(); 
     658 
     659        wp_redirect('edit.php'); 
     660    } 
     661 
     662    function _get_auth_sock() { 
     663        // Connect to https://www.google.com 
     664        if ( !$sock = @ fsockopen('ssl://www.google.com', 443, $errno, $errstr) ) { 
     665            $this->uh_oh( 
     666                __('Could not connect to https://www.google.com'), 
     667                __('There was a problem opening a secure connection to Google. This is what went wrong:'), 
     668                "$errstr ($errno)" 
     669            ); 
     670            return false; 
     671        } 
     672        return $sock; 
     673    } 
     674 
     675    function _get_blogger_sock($host = 'www.blogger.com') { 
     676        if ( !$sock = @ fsockopen($host, 80, $errno, $errstr) ) { 
     677            $this->uh_oh( 
     678                sprintf( __('Could not connect to %s'), $host ), 
     679                __('There was a problem opening a connection to Blogger. This is what went wrong:'), 
     680                "$errstr ($errno)" 
     681            ); 
     682            return false; 
     683        } 
     684        return $sock; 
     685    } 
     686 
     687    function _txrx( $sock, $request ) { 
     688        fwrite( $sock, $request ); 
     689        while ( ! feof( $sock ) ) 
     690            $response .= @ fread ( $sock, 8192 ); 
     691        fclose( $sock ); 
     692        return $response; 
     693    } 
     694 
     695    function revoke($token) { 
     696        $headers = array( 
     697            "GET /accounts/AuthSubRevokeToken HTTP/1.0", 
     698            "Authorization: AuthSub token=\"$token\"" 
     699        ); 
     700        $request = join( "\r\n", $headers ) . "\r\n\r\n"; 
     701        $sock = $this->_get_auth_sock( ); 
     702        if ( ! $sock ) return false; 
     703        $this->_txrx( $sock, $request ); 
     704    } 
     705 
    27706    function restart() { 
    28         delete_option('import-blogger'); 
    29         wp_redirect("admin.php?import=blogger"); 
    30         die(); 
    31     } 
    32  
    33     // Generates a string that will make the page reload in a specified interval. 
    34     function refresher($msec) { 
    35         if ( $msec ) 
    36             return "<html><head><script type='text/javascript'>window.onload=setTimeout('window.location.reload()', $msec);</script>\n</head>\n<body>\n"; 
    37         else 
    38             return "<html><head><script type='text/javascript'>window.onload=window.location.reload();</script>\n</head>\n<body>\n"; 
     707        global $wpdb; 
     708        $options = get_option( 'blogger_importer' ); 
     709         
     710        if ( isset( $options['token'] ) ) 
     711            $this->revoke( $options['token'] ); 
     712 
     713        delete_option('blogger_importer'); 
     714        $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'blogger_author'"); 
     715        wp_redirect('?import=blogger'); 
    39716    } 
    40717 
     
    61738 
    62739        return array("code" => $response_code, "header" => $response_header_array, "cookies" => $cookie_array, "body" => $response_body); 
    63     } 
    64  
    65     // Prints a form for the user to enter Blogger creds. 
    66     function login_form($text='') { 
    67         echo '<h1>' . __('Log in to Blogger') . "</h1>\n$text\n"; 
    68         echo '<form method="post" action="admin.php?import=blogger&amp;noheader=true&amp;step=0"><table><tr><td>' . __('Username') . ':</td><td><input type="text" name="user" /></td></tr><tr><td>' . __('Password') . ':</td><td><input type="password" name="pass" /></td><td><input type="submit" value="' . __('Start') . '" /></td></tr></table></form>'; 
    69         die; 
    70     } 
    71  
    72     // Sends creds to Blogger, returns the session cookies an array of headers. 
    73     function login_blogger($user, $pass) { 
    74         $_url = 'http://www.blogger.com/login.do'; 
    75         $params = "username=$user&password=$pass"; 
    76         $ch = curl_init(); 
    77         curl_setopt($ch, CURLOPT_POST,1); 
    78         curl_setopt($ch, CURLOPT_POSTFIELDS,$params); 
    79         curl_setopt($ch, CURLOPT_URL,$_url); 
    80         curl_setopt($ch, CURLOPT_USERAGENT, 'Blogger Exporter'); 
    81         curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); 
    82         curl_setopt($ch, CURLOPT_HEADER,1); 
    83         curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); 
    84         $response = curl_exec ($ch); 
    85  
    86         $response = $this->parse_response($response); 
    87  
    88         sleep(1); 
    89  
    90         return $response['cookies']; 
    91     } 
    92  
    93     // Requests page from Blogger, returns the response array. 
    94     function get_blogger($url, $header = '', $user=false, $pass=false) { 
    95         $ch = curl_init(); 
    96         if ($user && $pass) curl_setopt($ch, CURLOPT_USERPWD,"{$user}:{$pass}"); 
    97         curl_setopt($ch, CURLOPT_URL,$url); 
    98         curl_setopt($ch, CURLOPT_TIMEOUT, 10); 
    99         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
    100         curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 
    101         curl_setopt($ch, CURLOPT_USERAGENT, 'Blogger Exporter'); 
    102         curl_setopt($ch, CURLOPT_HEADER,1); 
    103         if (is_array($header)) curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 
    104         $response = curl_exec ($ch); 
    105  
    106         $response = $this->parse_response($response); 
    107         $response['url'] = $url; 
    108  
    109         if (curl_errno($ch)) { 
    110             print curl_error($ch); 
    111         } else { 
    112             curl_close($ch); 
    113         } 
    114  
    115         return $response; 
    116     } 
    117  
    118     // Posts data to Blogger, returns response array. 
    119     function post_blogger($url, $header = false, $paramary = false, $parse=true) { 
    120         $params = ''; 
    121         if ( is_array($paramary) ) { 
    122             foreach($paramary as $key=>$value) 
    123                 if($key && $value != '') 
    124                     $params.=$key."=".urlencode(stripslashes($value))."&"; 
    125         } 
    126         if ($user && $pass) $params .= "username=$user&password=$pass"; 
    127         $params = trim($params,'&'); 
    128         $ch = curl_init(); 
    129         curl_setopt($ch, CURLOPT_POST,1); 
    130         curl_setopt($ch, CURLOPT_POSTFIELDS,$params); 
    131         if ($user && $pass) curl_setopt($ch, CURLOPT_USERPWD,"{$user}:{$pass}"); 
    132         curl_setopt($ch, CURLOPT_URL,$url); 
    133         curl_setopt($ch, CURLOPT_USERAGENT, 'Blogger Exporter'); 
    134         curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 
    135         curl_setopt($ch, CURLOPT_HEADER,$parse); 
    136         curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); 
    137         if ($header) curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 
    138         $response = curl_exec ($ch); 
    139  
    140         if ($parse) { 
    141             $response = $this->parse_response($response); 
    142             $response['url'] = $url; 
    143             return $response; 
    144         } 
    145  
    146         return $response; 
    147     } 
    148  
    149     // Prints the list of blogs for import. 
    150     function show_blogs() { 
    151         global $import; 
    152         echo '<h1>' . __('Selecting a Blog') . "</h1>\n<ul>"; 
    153         foreach ( $this->import['blogs'] as $blog ) { 
    154             if (9 == $blog['nextstep']) $status = "100%"; 
    155             elseif (8 == $blog['nextstep']) $status = "90%"; 
    156             elseif (7 == $blog['nextstep']) $status = "82.5%"; 
    157             elseif (6 == $blog['nextstep']) $status = "75%"; 
    158             elseif (5 == $blog['nextstep']) $status = "57%"; 
    159             elseif (4 == $blog['nextstep']) $status = "28%"; 
    160             elseif (3 == $blog['nextstep']) $status = "14%"; 
    161             else $status = "0%"; 
    162             echo "\t<li><a href='admin.php?import=blogger&amp;noheader=true&amp;blog={$blog['id']}'>{$blog['title']}</a> $status</li>\n"; 
    163         } 
    164         die("</ul>\n"); 
    165     } 
    166  
    167     // Publishes. 
    168     function publish_blogger($i, $text) { 
    169         $head = $this->refresher(2000) . "<h1>$text</h1>\n"; 
    170         if ( ! strstr($this->import['blogs'][$_GET['blog']]['publish'][$i], 'http') ) { 
    171             // First call. Start the publish process with a fresh set of cookies. 
    172             $this->import['cookies'] = $this->login_blogger($this->import['user'], $this->import['pass']); 
    173             update_option('import-blogger', $this->import); 
    174             $paramary = array('blogID' => $_GET['blog'], 'all' => '1', 'republishAll' => 'Republish Entire Blog', 'publish' => '1', 'redirectUrl' => "/publish.do?blogID={$_GET['blog']}&inprogress=true"); 
    175  
    176             $response = $this->post_blogger("http://www.blogger.com/publish.do?blogID={$_GET['blog']}", $this->import['cookies'], $paramary); 
    177             if ( $response['code'] == '302' ) { 
    178                 $url = str_replace('publish.g', 'publish-body.g', $response['header']['Location']); 
    179                 $this->import['blogs'][$_GET['blog']]['publish'][$i] = $url; 
    180                 update_option('import-blogger', $this->import); 
    181                 $response = $this->get_blogger($url, $this->import['cookies']); 
    182                 preg_match('#<p class="progressIndicator">.*</p>#U', $response['body'], $matches); 
    183                 $progress = $matches[0]; 
    184                 die($head . $progress); 
    185             } else { 
    186                 $this->import['blogs'][$_GET['blog']]['publish'][$i] = false; 
    187                 update_option('import-blogger', $this->import); 
    188                 die($head); 
    189             } 
    190         } else { 
    191             // Subsequent call. Keep checking status until Blogger reports publish complete. 
    192             $url = $this->import['blogs'][$_GET['blog']]['publish'][$i]; 
    193             $response = $this->get_blogger($url, $this->import['cookies']); 
    194             if ( preg_match('#<p class="progressIndicator">.*</p>#U', $response['body'], $matches) ) { 
    195                 $progress = $matches[0]; 
    196                 if ( strstr($progress, '100%') ) { 
    197                     $this->set_next_step($i); 
    198                     $progress .= '<p>'.__('Moving on...').'</p>'; 
    199                 } 
    200                 die($head . $progress); 
    201             } else { 
    202                 $this->import['blogs'][$_GET['blog']]['publish'][$i] = false; 
    203                 update_option('import-blogger', $this->import); 
    204                 die("$head<p>" . __('Trying again...') . '</p>'); 
    205             } 
    206         } 
    207     } 
    208  
    209     // Sets next step, saves options 
    210     function set_next_step($step) { 
    211         $this->import['blogs'][$_GET['blog']]['nextstep'] = $step; 
    212         update_option('import-blogger', $this->import); 
    213     } 
    214  
    215     // Redirects to next step 
    216<