WordPress.org

Make WordPress Core

Ticket #4622: blogger.php.patch

File blogger.php.patch, 35.9 KB (added by clazh, 7 years ago)

Blogger.php File with Patched code

Line 
1<?php
2
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
7class Blogger_Import {
8
9        // Shows the welcome screen and the magic auth link.
10        function greet() {
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: www2.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;
353                else
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                        preg_match("/([^\/]+)\.html$/", $entry->old_permalink ,$matches);
522                        $wpdb->query("UPDATE $wpdb->posts SET post_name = '" . $matches[1] . "' WHERE ID = '$post_id'");
523
524                        wp_create_categories( array_map( 'addslashes', $entry->categories ), $post_id );
525
526                        $author = $this->no_apos( strip_tags( $entry->author ) );
527
528                        add_post_meta( $post_id, 'blogger_blog', $this->blogs[$importing_blog]['host'], true );
529                        add_post_meta( $post_id, 'blogger_author', $author, true );
530                        add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true );
531
532                        $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
533                        ++$this->blogs[$importing_blog]['posts_done'];
534                }
535                $this->save_vars();
536        }
537
538        function import_comment( $entry ) {
539                global $importing_blog;
540
541                // Drop the #fragment and we have the comment's old post permalink.
542                foreach ( $entry->links as $link ) {
543                        if ( $link['rel'] == 'alternate' ) {
544                                $parts = parse_url( $link['href'] );
545                                $entry->old_permalink = $parts['fragment'];
546                                $entry->old_post_permalink = $parts['path'];
547                                break;
548                        }
549                }
550
551                $comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink];
552                preg_match('#<name>(.+?)</name>.*(?:\<uri>(.+?)</uri>)?#', $entry->author, $matches);
553                $comment_author  = addslashes( $this->no_apos( strip_tags( (string) $matches[1] ) ) );
554                $comment_author_url = addslashes( $this->no_apos( strip_tags( (string) $matches[2] ) ) );
555                $comment_date    = $this->convert_date( $entry->updated );
556                $comment_content = addslashes( $this->no_apos( html_entity_decode( $entry->content ) ) );
557
558                // Clean up content
559                $comment_content = preg_replace('|<(/?[A-Z]+)|e', "'<' . strtolower('$1')", $comment_content);
560                $comment_content = str_replace('<br>', '<br />', $comment_content);
561                $comment_content = str_replace('<hr>', '<hr />', $comment_content);
562
563                // Checks for duplicates
564                if (
565                        isset( $this->blogs[$importing_blog]['comments'][$entry->old_permalink] ) ||
566                        comment_exists( $comment_author, $comment_date )
567                ) {
568                        ++$this->blogs[$importing_blog]['comments_skipped'];
569                } else {
570                        $comment = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_date', 'comment_content');
571
572                        $comment_id = wp_insert_comment($comment);
573
574                        $this->blogs[$importing_blog]['comments'][$entry->old_permalink] = $comment_id;
575
576                        ++$this->blogs[$importing_blog]['comments_done'];
577                }
578                $this->save_vars();
579        }
580
581        function get_js_status($blog = false) {
582                global $importing_blog;
583                if ( $blog === false )
584                        $blog = $this->blogs[$importing_blog];
585                else
586                        $blog = $this->blogs[$blog];
587                $p1 = isset( $blog['posts_done'] ) ? (int) $blog['posts_done'] : 0;
588                $p2 = isset( $blog['total_posts'] ) ? (int) $blog['total_posts'] : 0;
589                $c1 = isset( $blog['comments_done'] ) ? (int) $blog['comments_done'] : 0;
590                $c2 = isset( $blog['total_comments'] ) ? (int) $blog['total_comments'] : 0;
591                return "{p1:$p1,p2:$p2,c1:$c1,c2:$c2}";
592        }
593
594        function get_author_form($blog = false) {
595                global $importing_blog, $wpdb, $current_user;
596                if ( $blog === false )
597                        $blog = & $this->blogs[$importing_blog];
598                else
599                        $blog = & $this->blogs[$blog];
600
601                if ( !isset( $blog['authors'] ) ) {
602                        $post_ids = array_values($blog['posts']);
603                        $authors = (array) $wpdb->get_col("SELECT DISTINCT meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN (" . join( ',', $post_ids ) . ")");
604                        $blog['authors'] = array_map(null, $authors, array_fill(0, count($authors), $current_user->ID));
605                        $this->save_vars();
606                }
607
608                $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.');
609                $heading = __('Author mapping');
610                $blogtitle = "{$blog['title']} ({$blog['host']})";
611                $mapthis = __('Blogger username');
612                $tothis = __('WordPress login');
613                $submit = js_escape( __('Save Changes &raquo;') );
614
615                foreach ( $blog['authors'] as $i => $author )
616                        $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>";
617
618                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>";
619        }
620
621        function get_user_options($current) {
622                global $wpdb, $importer_users;
623                if ( ! isset( $importer_users ) )
624                        $importer_users = (array) get_users_of_blog();
625
626                foreach ( $importer_users as $user ) {
627                        $sel = ( $user->user_id == $current ) ? " selected='selected'" : '';
628                        $options .= "<option value='$user->user_id'$sel>$user->display_name</option>";
629                }
630
631                return $options;
632        }
633
634        function save_authors() {
635                global $importing_blog, $wpdb;
636                $authors = (array) $_POST['authors'];
637
638                $host = $this->blogs[$importing_blog]['host'];
639
640                // Get an array of posts => authors
641                $post_ids = (array) $wpdb->get_col("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'blogger_blog' AND meta_value = '$host'");
642                $post_ids = join( ',', $post_ids );
643                $results = (array) $wpdb->get_results("SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN ($post_ids)");
644                foreach ( $results as $row )
645                        $authors_posts[$row->post_id] = $row->meta_value;
646
647                foreach ( $authors as $author => $user_id ) {
648                        $user_id = (int) $user_id;
649
650                        // Skip authors that haven't been changed
651                        if ( $user_id == $this->blogs[$importing_blog]['authors'][$author][1] )
652                                continue;
653
654                        // Get a list of the selected author's posts
655                        $post_ids = (array) array_keys( $authors_posts, $this->blogs[$importing_blog]['authors'][$author][0] );
656                        $post_ids = join( ',', $post_ids);
657
658                        $wpdb->query("UPDATE $wpdb->posts SET post_author = $user_id WHERE id IN ($post_ids)");
659                        $this->blogs[$importing_blog]['authors'][$author][1] = $user_id;
660                }
661                $this->save_vars();
662
663                wp_redirect('edit.php');
664        }
665
666        function _get_auth_sock() {
667                // Connect to https://www.google.com
668                if ( !$sock = @ fsockopen('ssl://www.google.com', 443, $errno, $errstr) ) {
669                        $this->uh_oh(
670                                __('Could not connect to https://www.google.com'),
671                                __('There was a problem opening a secure connection to Google. This is what went wrong:'),
672                                "$errstr ($errno)"
673                        );
674                        return false;
675                }
676                return $sock;
677        }
678
679        function _get_blogger_sock($host = 'www2.blogger.com') {
680                if ( !$sock = @ fsockopen($host, 80, $errno, $errstr) ) {
681                        $this->uh_oh(
682                                sprintf( __('Could not connect to %s'), $host ),
683                                __('There was a problem opening a connection to Blogger. This is what went wrong:'),
684                                "$errstr ($errno)"
685                        );
686                        return false;
687                }
688                return $sock;
689        }
690
691        function _txrx( $sock, $request ) {
692                fwrite( $sock, $request );
693                while ( ! feof( $sock ) )
694                        $response .= @ fread ( $sock, 8192 );
695                fclose( $sock );
696                return $response;
697        }
698
699        function revoke($token) {
700                $headers = array(
701                        "GET /accounts/AuthSubRevokeToken HTTP/1.0",
702                        "Authorization: AuthSub token=\"$token\""
703                );
704                $request = join( "\r\n", $headers ) . "\r\n\r\n";
705                $sock = $this->_get_auth_sock( );
706                if ( ! $sock ) return false;
707                $this->_txrx( $sock, $request );
708        }
709
710        function restart() {
711                global $wpdb;
712                $options = get_option( 'blogger_importer' );
713
714                if ( isset( $options['token'] ) )
715                        $this->revoke( $options['token'] );
716
717                delete_option('blogger_importer');
718                $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'blogger_author'");
719                wp_redirect('?import=blogger');
720        }
721
722        // Returns associative array of code, header, cookies, body. Based on code from php.net.
723        function parse_response($this_response) {
724                // Split response into header and body sections
725                list($response_headers, $response_body) = explode("\r\n\r\n", $this_response, 2);
726                $response_header_lines = explode("\r\n", $response_headers);
727
728                // First line of headers is the HTTP response code
729                $http_response_line = array_shift($response_header_lines);
730                if(preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@',$http_response_line, $matches)) { $response_code = $matches[1]; }
731
732                // put the rest of the headers in an array
733                $response_header_array = array();
734                foreach($response_header_lines as $header_line) {
735                        list($header,$value) = explode(': ', $header_line, 2);
736                        $response_header_array[$header] .= $value."\n";
737                }
738
739                $cookie_array = array();
740                $cookies = explode("\n", $response_header_array["Set-Cookie"]);
741                foreach($cookies as $this_cookie) { array_push($cookie_array, "Cookie: ".$this_cookie); }
742
743                return array("code" => $response_code, "header" => $response_header_array, "cookies" => $cookie_array, "body" => $response_body);
744        }
745
746        // Step 9: Congratulate the user
747        function congrats() {
748                $blog = (int) $_GET['blog'];
749                echo '<h1>'.__('Congratulations!').'</h1><p>'.__('Now that you have imported your Blogger blog into WordPress, what are you going to do? Here are some suggestions:').'</p><ul><li>'.__('That was hard work! Take a break.').'</li>';
750                if ( count($this->import['blogs']) > 1 )
751                        echo '<li>'.__('In case you haven\'t done it already, you can import the posts from your other blogs:'). $this->show_blogs() . '</li>';
752                if ( $n = count($this->import['blogs'][$blog]['newusers']) )
753                        echo '<li>'.sprintf(__('Go to <a href="%s" target="%s">Authors &amp; Users</a>, where you can modify the new user(s) or delete them. If you want to make all of the imported posts yours, you will be given that option when you delete the new authors.'), 'users.php', '_parent').'</li>';
754                echo '<li>'.__('For security, click the link below to reset this importer.').'</li>';
755                echo '</ul>';
756        }
757
758        // Figures out what to do, then does it.
759        function start() {
760                if ( isset($_POST['restart']) )
761                        $this->restart();
762
763                $options = get_option('blogger_importer');
764
765                if ( is_array($options) )
766                        foreach ( $options as $key => $value )
767                                $this->$key = $value;
768
769                if ( isset( $_REQUEST['blog'] ) ) {
770                        $blog = is_array($_REQUEST['blog']) ? array_shift( array_keys( $_REQUEST['blog'] ) ) : $_REQUEST['blog'];
771                        $blog = (int) $blog;
772                        $this->import_blog( $blog );
773                } elseif ( isset($_GET['token']) )
774                        $this->auth();
775                elseif ( $this->token && $this->token_is_valid() )
776                        $this->show_blogs();
777                else
778                        $this->greet();
779
780                $saved = $this->save_vars();
781
782                if ( $saved && !isset($_GET['noheader']) ) {
783                        $restart = __('Restart');
784                        $message = __('We have saved some information about your Blogger account in your WordPress database. Clearing this information will allow you to start over. Restarting will not affect any posts you have already imported. If you attempt to re-import a blog, duplicate posts and comments will be skipped.');
785                        $submit = __('Clear account information');
786                        echo "<div class='wrap'><h2>$restart</h2><p>$message</p><form method='post' action='?import=blogger&noheader=true'><p class='submit' style='text-align:left;'><input type='submit' value='$submit' name='restart' /></p></form></div>";
787                }
788        }
789
790        function save_vars() {
791                $vars = get_object_vars($this);
792                update_option( 'blogger_importer', $vars );
793
794                return !empty($vars);
795        }
796
797        function admin_head() {
798?>
799<style type="text/css">
800td { text-align: center; line-height: 2em;}
801thead td { font-weight: bold; }
802.bar {
803        width: 200px;
804        text-align: left;
805        line-height: 2em;
806        padding: 0px;
807}
808.ind {
809        position: absolute;
810        background-color: #83B4D8;
811        width: 1px;
812        z-index: 9;
813}
814.stat {
815        z-index: 10;
816        position: relative;
817        text-align: center;
818}
819.submit {
820        text-align: center !important;
821}
822</style>
823<?php
824        }
825
826        function Blogger_Import() {
827                global $importer_started;
828                $importer_started = time();
829                if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) {
830                        wp_enqueue_script('jquery');
831                        add_action('admin_head', array(&$this, 'admin_head'));
832                }
833        }
834}
835
836$blogger_import = new Blogger_Import();
837
838register_importer('blogger', __('Blogger'), __('Import posts, comments, and users from a Blogger blog'), array ($blogger_import, 'start'));
839
840class AtomEntry {
841        var $links = array();
842        var $categories = array();
843}
844
845class AtomParser {
846
847        var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights');
848        var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft','author');
849
850        var $depth = 0;
851        var $indent = 2;
852        var $in_content;
853        var $ns_contexts = array();
854        var $ns_decls = array();
855        var $is_xhtml = false;
856        var $skipped_div = false;
857
858        var $entry;
859
860        function AtomParser() {
861
862                $this->entry = new AtomEntry();
863                $this->map_attrs_func = create_function('$k,$v', 'return "$k=\"$v\"";');
864                $this->map_xmlns_func = create_function('$p,$n', '$xd = "xmlns"; if(strlen($n[0])>0) $xd .= ":{$n[0]}"; return "{$xd}=\"{$n[1]}\"";');
865        }
866
867        function parse($xml) {
868
869                global $app_logging;
870                array_unshift($this->ns_contexts, array());
871
872                $parser = xml_parser_create_ns();
873                xml_set_object($parser, $this);
874                xml_set_element_handler($parser, "start_element", "end_element");
875                xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);
876                xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0);
877                xml_set_character_data_handler($parser, "cdata");
878                xml_set_default_handler($parser, "_default");
879                xml_set_start_namespace_decl_handler($parser, "start_ns");
880                xml_set_end_namespace_decl_handler($parser, "end_ns");
881
882                $contents = "";
883
884                xml_parse($parser, $xml);
885
886                xml_parser_free($parser);
887
888                return true;
889        }
890
891        function start_element($parser, $name, $attrs) {
892
893                $tag = array_pop(split(":", $name));
894
895                array_unshift($this->ns_contexts, $this->ns_decls);
896
897                $this->depth++;
898
899                if(!empty($this->in_content)) {
900                        $attrs_prefix = array();
901
902                        // resolve prefixes for attributes
903                        foreach($attrs as $key => $value) {
904                                $attrs_prefix[$this->ns_to_prefix($key)] = $this->xml_escape($value);
905                        }
906                        $attrs_str = join(' ', array_map($this->map_attrs_func, array_keys($attrs_prefix), array_values($attrs_prefix)));
907                        if(strlen($attrs_str) > 0) {
908                                $attrs_str = " " . $attrs_str;
909                        }
910
911                        $xmlns_str = join(' ', array_map($this->map_xmlns_func, array_keys($this->ns_contexts[0]), array_values($this->ns_contexts[0])));
912                        if(strlen($xmlns_str) > 0) {
913                                $xmlns_str = " " . $xmlns_str;
914                        }
915
916                        // handle self-closing tags (case: a new child found right-away, no text node)
917                        if(count($this->in_content) == 2) {
918                                array_push($this->in_content, ">");
919                        }
920                 
921                        array_push($this->in_content, "<". $this->ns_to_prefix($name) ."{$xmlns_str}{$attrs_str}");
922                } else if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) {
923                        $this->in_content = array();
924                        $this->is_xhtml = $attrs['type'] == 'xhtml';
925                        array_push($this->in_content, array($tag,$this->depth));
926                } else if($tag == 'link') {
927                        array_push($this->entry->links, $attrs);
928                } else if($tag == 'category') {
929                        array_push($this->entry->categories, $attrs['term']);
930                }
931
932                $this->ns_decls = array();
933        }
934
935        function end_element($parser, $name) {
936
937                $tag = array_pop(split(":", $name));
938
939                if(!empty($this->in_content)) {
940                        if($this->in_content[0][0] == $tag &&
941                        $this->in_content[0][1] == $this->depth) {
942                                array_shift($this->in_content);
943                                if($this->is_xhtml) {
944                                        $this->in_content = array_slice($this->in_content, 2, count($this->in_content)-3);
945                                }
946                                $this->entry->$tag = join('',$this->in_content);
947                                $this->in_content = array();
948                        } else {
949                                $endtag = $this->ns_to_prefix($name);
950                                if (strpos($this->in_content[count($this->in_content)-1], '<' . $endtag) !== false) {
951                                        array_push($this->in_content, "/>");
952                                } else {
953                                        array_push($this->in_content, "</$endtag>");
954                                }
955                        }
956                }
957
958                array_shift($this->ns_contexts);
959
960                #print str_repeat(" ", $this->depth * $this->indent) . "end_element('$name')" ."\n";
961
962                $this->depth--;
963        }
964
965        function start_ns($parser, $prefix, $uri) {
966                #print str_repeat(" ", $this->depth * $this->indent) . "starting: " . $prefix . ":" . $uri . "\n";
967                array_push($this->ns_decls, array($prefix,$uri));
968        }
969
970        function end_ns($parser, $prefix) {
971                #print str_repeat(" ", $this->depth * $this->indent) . "ending: #" . $prefix . "#\n";
972        }
973
974        function cdata($parser, $data) {
975                #print str_repeat(" ", $this->depth * $this->indent) . "data: #" . $data . "#\n";
976                if(!empty($this->in_content)) {
977                        // handle self-closing tags (case: text node found, need to close element started)
978                        if (strpos($this->in_content[count($this->in_content)-1], '<') !== false) {
979                                array_push($this->in_content, ">");
980                        }
981                        array_push($this->in_content, $this->xml_escape($data));
982                }
983        }
984
985        function _default($parser, $data) {
986                # when does this gets called?
987        }
988
989
990        function ns_to_prefix($qname) {
991                $components = split(":", $qname);
992                $name = array_pop($components);
993
994                if(!empty($components)) {
995                        $ns = join(":",$components);
996                        foreach($this->ns_contexts as $context) {
997                                foreach($context as $mapping) {
998                                        if($mapping[1] == $ns && strlen($mapping[0]) > 0) {
999                                                return "$mapping[0]:$name";
1000                                        }
1001                                }
1002                        }
1003                }
1004                return $name;
1005        }
1006
1007        function xml_escape($string)
1008        {
1009                         return str_replace(array('&','"',"'",'<','>'),
1010                                array('&amp;','&quot;','&apos;','&lt;','&gt;'),
1011                                $string );
1012        }
1013}
1014
1015?>