3 define( 'MAX_RESULTS', 50 ); // How many records per GData query
4 define( 'MAX_EXECUTION_TIME', 20 ); // How many seconds to let the script run
5 define( 'STATUS_INTERVAL', 3 ); // How many seconds between status bar updates
9 // Shows the welcome screen and the magic auth link.
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');
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' />
33 function uh_oh($title, $message, $info) {
34 echo "<div class='wrap'><h2>$title</h2><p>$message</p><pre>$info</pre></div>";
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'] );
41 "GET /accounts/AuthSubSessionToken HTTP/1.0",
42 "Authorization: AuthSub token=\"$token\""
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] ) ) {
51 __( 'Authorization failed' ),
52 __( 'Something went wrong. If the problem persists, send this info to support:' ),
53 htmlspecialchars($response)
57 $this->token = $matches[1];
59 wp_redirect( remove_query_arg( array( 'token', 'noheader' ) ) );
62 function get_token_info() {
64 "GET /accounts/AuthSubTokenInfo HTTP/1.0",
65 "Authorization: AuthSub token=\"$this->token\""
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);
74 function token_is_valid() {
75 $info = $this->get_token_info();
77 if ( $info['code'] == 200 )
83 function show_blogs($iter = 0) {
84 if ( empty($this->blogs) ) {
86 "GET /feeds/default/blogs HTTP/1.0",
87 "Host: www.blogger.com",
88 "Authorization: AuthSub token=\"$this->token\""
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 );
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);
101 $this->title = $vals[$index['TITLE'][0]]['value'];
103 // Give it a few retries... this step often flakes out the first time.
104 if ( empty( $index['ENTRY'] ) ) {
106 return $this->show_blogs($iter + 1);
109 __('Trouble signing in'),
110 __('We were not able to gain access to your account. Try starting over.'),
117 foreach ( $index['ENTRY'] as $i ) {
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'];
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;
141 if ( empty( $this->blogs ) ) {
143 __('No blogs found'),
144 __('We were able to log in but there were no blogs. Try a different account next time.'),
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.');
166 $interval = STATUS_INTERVAL * 1000;
168 foreach ( $this->blogs as $i => $blog ) {
169 if ( $blog['mode'] == 'init' )
171 elseif ( $blog['mode'] == 'posts' || $blog['mode'] == 'comments' )
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'> </div><div id='pstat$i' class='stat'>$pdone/{$blog['total_posts']}</div>";
180 $cstat = "<div class='ind' id='cind$i'> </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";
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>";
186 <script type='text/javascript'>
187 var strings = {cont:'$continue',stop:'$stop',stopping:'$stopping',authors:'$authors',nothing:'$nothing'};
189 function blog(i, title, mode, status){
193 this.status = status;
194 this.button = document.getElementById('submit'+this.blog);
205 jQuery.post('admin.php?import=blogger&noheader=true',{blog:this.blog},function(text,result){blogs[i].kickd(text,result)});
210 jQuery.post('admin.php?import=blogger&noheader=true&status=true',{blog:this.blog},function(text,result){blogs[i].checkd(text,result)});
212 kickd: function(text, result) {
213 if ( result == 'error' ) {
214 // TODO: exception handling
216 setTimeout('blogs['+this.blog+'].kick()', 1000);
218 if ( text == 'done' ) {
221 } else if ( text == 'nothing' ) {
224 } else if ( text == 'continue' ) {
226 } else if ( this.mode = 'stopped' )
227 jQuery(this.button).attr('value', strings.cont);
231 checkd: function(text, result) {
232 if ( result == 'error' ) {
233 // TODO: exception handling
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);
239 if ( this.cont || this.kicks > 0 )
240 setTimeout('blogs['+this.blog+'].check()', $interval);
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');
252 this.mode = 'authors';
253 jQuery(this.button).attr('value', strings.authors);
255 nothing: function() {
256 this.mode = 'nothing';
257 jQuery(this.button).remove();
258 alert(strings.nothing);
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});
270 jQuery(this.button).bind('click', function(){return blogs[i].click();});
275 if ( this.mode == 'init' || this.mode == 'stopped' || this.mode == 'posts' || this.mode == 'comments' ) {
276 this.mode = 'started';
278 jQuery(this.button).attr('value', strings.stop);
279 } else if ( this.mode == 'started' ) {
280 return false; // let it run...
281 this.mode = 'stopped';
283 if ( this.checks > 0 || this.kicks > 0 ) {
284 this.mode = 'stopping';
285 jQuery(this.button).attr('value', strings.stopping);
287 jQuery(this.button).attr('value', strings.cont);
289 } else if ( this.mode == 'authors' ) {
290 document.location = 'index.php?import=blogger&authors=1&blog='+this.blog;
291 //this.mode = 'authors2';
298 jQuery.each(blogs, function(i, me){me.init();});
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 )
310 function get_total_results($type, $host) {
312 "GET /feeds/$type/default?max-results=1&start-index=2 HTTP/1.0",
314 "Authorization: AuthSub token=\"$this->token\""
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;
328 function import_blog($blogID) {
329 global $importing_blog;
330 $importing_blog = $blogID;
332 if ( isset($_GET['authors']) )
333 return print($this->get_author_form());
335 header('Content-Type: text/plain');
337 if ( isset($_GET['status']) )
338 die($this->get_js_status());
340 if ( isset($_GET['saveauthors']) )
341 die($this->save_authors());
343 $blog = $this->blogs[$blogID];
344 $total_results = $this->get_total_results('posts', $blog['host']);
345 $this->blogs[$importing_blog]['total_posts'] = $total_results;
347 $start_index = $total_results - MAX_RESULTS + 1;
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;
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;
362 $index = $struct = $entries = array();
364 "GET /feeds/posts/default?$query HTTP/1.0",
365 "Host: {$blog['host']}",
366 "Authorization: AuthSub token=\"$this->token\""
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 );
373 $response = $this->parse_response( $response );
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 $result = $this->import_post($AtomParser->entry);
384 if ( is_wp_error( $result ) )
390 // Get the 'previous' query string which we'll use on the next iteration
392 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches);
393 if ( count( $matches[1] ) )
394 foreach ( $matches[1] as $match )
395 if ( preg_match('/rel=.previous./', $match) )
396 $query = html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match) );
399 parse_str($query, $q);
400 $this->blogs[$importing_blog]['posts_start_index'] = (int) $q['start-index'];
402 $this->blogs[$importing_blog]['posts_start_index'] = 0;
404 } while ( !empty( $query ) && $this->have_time() );
407 $total_results = $this->get_total_results( 'comments', $blog['host'] );
408 $this->blogs[$importing_blog]['total_comments'] = $total_results;
410 if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) )
411 $start_index = (int) $this->blogs[$importing_blog]['comments_start_index'];
412 elseif ( $total_results > MAX_RESULTS )
413 $start_index = $total_results - MAX_RESULTS + 1;
417 if ( $start_index > 0 ) {
418 // Grab all the comments
419 $this->blogs[$importing_blog]['mode'] = 'comments';
420 $query = "start-index=$start_index&max-results=" . MAX_RESULTS;
422 $index = $struct = $entries = array();
424 "GET /feeds/comments/default?$query HTTP/1.0",
425 "Host: {$blog['host']}",
426 "Authorization: AuthSub token=\"$this->token\""
428 $request = join( "\r\n", $headers ) . "\r\n\r\n";
429 $sock = $this->_get_blogger_sock( $blog['host'] );
430 if ( ! $sock ) return; // TODO: Error handling
431 $response = $this->_txrx( $sock, $request );
433 $response = $this->parse_response( $response );
435 // Extract the comments and send for insertion
436 preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches );
437 if ( count( $matches[0] ) ) {
438 $entries = array_reverse( $matches[0] );
439 foreach ( $entries as $entry ) {
440 $entry = "<feed>$entry</feed>";
441 $AtomParser = new AtomParser();
442 $AtomParser->parse( $entry );
443 $this->import_comment($AtomParser->entry);
448 // Get the 'previous' query string which we'll use on the next iteration
450 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches);
451 if ( count( $matches[1] ) )
452 foreach ( $matches[1] as $match )
453 if ( preg_match('/rel=.previous./', $match) )
454 $query = html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match) );
456 parse_str($query, $q);
458 $this->blogs[$importing_blog]['comments_start_index'] = (int) $q['start-index'];
460 } while ( !empty( $query ) && $this->have_time() );
462 $this->blogs[$importing_blog]['mode'] = 'authors';
464 if ( !$this->blogs[$importing_blog]['posts_done'] && !$this->blogs[$importing_blog]['comments_done'] )
466 do_action('import_done', 'blogger');
470 function convert_date( $date ) {
471 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);
472 $offset = iso8601_timezone_to_offset( $date_bits[7] );
473 $timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]);
474 $timestamp -= $offset; // Convert from Blogger local time to GMT
475 $timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time
476 return gmdate('Y-m-d H:i:s', $timestamp);
479 function no_apos( $string ) {
480 return str_replace( ''', "'", $string);
483 function min_whitespace( $string ) {
484 return preg_replace( '|\s+|', ' ', $string );
487 function import_post( $entry ) {
488 global $wpdb, $importing_blog;
490 // The old permalink is all Blogger gives us to link comments to their posts.
491 if ( isset( $entry->draft ) )
495 foreach ( $entry->links as $link ) {
496 if ( $link['rel'] == $rel ) {
497 $parts = parse_url( $link['href'] );
498 $entry->old_permalink = $parts['path'];
503 $post_date = $this->convert_date( $entry->published );
504 $post_content = trim( addslashes( $this->no_apos( html_entity_decode( $entry->content ) ) ) );
505 $post_title = trim( addslashes( $this->no_apos( $this->min_whitespace( $entry->title ) ) ) );
506 $post_status = isset( $entry->draft ) ? 'draft' : 'publish';
509 $post_content = preg_replace('|<(/?[A-Z]+)|e', "'<' . strtolower('$1')", $post_content);
510 $post_content = str_replace('<br>', '<br />', $post_content);
511 $post_content = str_replace('<hr>', '<hr />', $post_content);
513 // Checks for duplicates
514 if ( isset( $this->blogs[$importing_blog]['posts'][$entry->old_permalink] ) ) {
515 ++$this->blogs[$importing_blog]['posts_skipped'];
516 } elseif ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) {
517 $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
518 ++$this->blogs[$importing_blog]['posts_skipped'];
520 $post = compact('post_date', 'post_content', 'post_title', 'post_status');
522 $post_id = wp_insert_post($post);
523 if ( is_wp_error( $post_id ) )
526 wp_create_categories( array_map( 'addslashes', $entry->categories ), $post_id );
528 $author = $this->no_apos( strip_tags( $entry->author ) );
530 add_post_meta( $post_id, 'blogger_blog', $this->blogs[$importing_blog]['host'], true );
531 add_post_meta( $post_id, 'blogger_author', $author, true );
532 add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true );
534 $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
535 ++$this->blogs[$importing_blog]['posts_done'];
541 function import_comment( $entry ) {
542 global $importing_blog;
544 // Drop the #fragment and we have the comment's old post permalink.
545 foreach ( $entry->links as $link ) {
546 if ( $link['rel'] == 'alternate' ) {
547 $parts = parse_url( $link['href'] );
548 $entry->old_permalink = $parts['fragment'];
549 $entry->old_post_permalink = $parts['path'];
554 $comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink];
555 preg_match('#<name>(.+?)</name>.*(?:\<uri>(.+?)</uri>)?#', $entry->author, $matches);
556 $comment_author = addslashes( $this->no_apos( strip_tags( (string) $matches[1] ) ) );
557 $comment_author_url = addslashes( $this->no_apos( strip_tags( (string) $matches[2] ) ) );
558 $comment_date = $this->convert_date( $entry->updated );
559 $comment_content = addslashes( $this->no_apos( html_entity_decode( $entry->content ) ) );
562 $comment_content = preg_replace('|<(/?[A-Z]+)|e', "'<' . strtolower('$1')", $comment_content);
563 $comment_content = str_replace('<br>', '<br />', $comment_content);
564 $comment_content = str_replace('<hr>', '<hr />', $comment_content);
566 // Checks for duplicates
568 isset( $this->blogs[$importing_blog]['comments'][$entry->old_permalink] ) ||
569 comment_exists( $comment_author, $comment_date )
571 ++$this->blogs[$importing_blog]['comments_skipped'];
573 $comment = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_date', 'comment_content');
575 $comment_id = wp_insert_comment($comment);
577 $this->blogs[$importing_blog]['comments'][$entry->old_permalink] = $comment_id;
579 ++$this->blogs[$importing_blog]['comments_done'];
584 function get_js_status($blog = false) {
585 global $importing_blog;
586 if ( $blog === false )
587 $blog = $this->blogs[$importing_blog];
589 $blog = $this->blogs[$blog];
590 $p1 = isset( $blog['posts_done'] ) ? (int) $blog['posts_done'] : 0;
591 $p2 = isset( $blog['total_posts'] ) ? (int) $blog['total_posts'] : 0;
592 $c1 = isset( $blog['comments_done'] ) ? (int) $blog['comments_done'] : 0;
593 $c2 = isset( $blog['total_comments'] ) ? (int) $blog['total_comments'] : 0;
594 return "{p1:$p1,p2:$p2,c1:$c1,c2:$c2}";
597 function get_author_form($blog = false) {
598 global $importing_blog, $wpdb, $current_user;
599 if ( $blog === false )
600 $blog = & $this->blogs[$importing_blog];
602 $blog = & $this->blogs[$blog];
604 if ( !isset( $blog['authors'] ) ) {
605 $post_ids = array_values($blog['posts']);
606 $authors = (array) $wpdb->get_col("SELECT DISTINCT meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN (" . join( ',', $post_ids ) . ")");
607 $blog['authors'] = array_map(null, $authors, array_fill(0, count($authors), $current_user->ID));
611 $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.');
612 $heading = __('Author mapping');
613 $blogtitle = "{$blog['title']} ({$blog['host']})";
614 $mapthis = __('Blogger username');
615 $tothis = __('WordPress login');
616 $submit = js_escape( __('Save Changes »') );
618 foreach ( $blog['authors'] as $i => $author )
619 $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>";
621 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>";
624 function get_user_options($current) {
625 global $wpdb, $importer_users;
626 if ( ! isset( $importer_users ) )
627 $importer_users = (array) get_users_of_blog();
629 foreach ( $importer_users as $user ) {
630 $sel = ( $user->user_id == $current ) ? " selected='selected'" : '';
631 $options .= "<option value='$user->user_id'$sel>$user->display_name</option>";
637 function save_authors() {
638 global $importing_blog, $wpdb;
639 $authors = (array) $_POST['authors'];
641 $host = $this->blogs[$importing_blog]['host'];
643 // Get an array of posts => authors
644 $post_ids = (array) $wpdb->get_col("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'blogger_blog' AND meta_value = '$host'");
645 $post_ids = join( ',', $post_ids );
646 $results = (array) $wpdb->get_results("SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN ($post_ids)");
647 foreach ( $results as $row )
648 $authors_posts[$row->post_id] = $row->meta_value;
650 foreach ( $authors as $author => $user_id ) {
651 $user_id = (int) $user_id;
653 // Skip authors that haven't been changed
654 if ( $user_id == $this->blogs[$importing_blog]['authors'][$author][1] )
657 // Get a list of the selected author's posts
658 $post_ids = (array) array_keys( $authors_posts, $this->blogs[$importing_blog]['authors'][$author][0] );
659 $post_ids = join( ',', $post_ids);
661 $wpdb->query("UPDATE $wpdb->posts SET post_author = $user_id WHERE id IN ($post_ids)");
662 $this->blogs[$importing_blog]['authors'][$author][1] = $user_id;
666 wp_redirect('edit.php');
669 function _get_auth_sock() {
670 // Connect to https://www.google.com
671 if ( !$sock = @ fsockopen('ssl://www.google.com', 443, $errno, $errstr) ) {
673 __('Could not connect to https://www.google.com'),
674 __('There was a problem opening a secure connection to Google. This is what went wrong:'),
682 function _get_blogger_sock($host = 'www2.blogger.com') {
683 if ( !$sock = @ fsockopen($host, 80, $errno, $errstr) ) {
685 sprintf( __('Could not connect to %s'), $host ),
686 __('There was a problem opening a connection to Blogger. This is what went wrong:'),
694 function _txrx( $sock, $request ) {
695 fwrite( $sock, $request );
696 while ( ! feof( $sock ) )
697 $response .= @ fread ( $sock, 8192 );
702 function revoke($token) {
704 "GET /accounts/AuthSubRevokeToken HTTP/1.0",
705 "Authorization: AuthSub token=\"$token\""
707 $request = join( "\r\n", $headers ) . "\r\n\r\n";
708 $sock = $this->_get_auth_sock( );
709 if ( ! $sock ) return false;
710 $this->_txrx( $sock, $request );
715 $options = get_option( 'blogger_importer' );
717 if ( isset( $options['token'] ) )
718 $this->revoke( $options['token'] );
720 delete_option('blogger_importer');
721 $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'blogger_author'");
722 wp_redirect('?import=blogger');
725 // Returns associative array of code, header, cookies, body. Based on code from php.net.
726 function parse_response($this_response) {
727 // Split response into header and body sections
728 list($response_headers, $response_body) = explode("\r\n\r\n", $this_response, 2);
729 $response_header_lines = explode("\r\n", $response_headers);
731 // First line of headers is the HTTP response code
732 $http_response_line = array_shift($response_header_lines);
733 if(preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@',$http_response_line, $matches)) { $response_code = $matches[1]; }
735 // put the rest of the headers in an array
736 $response_header_array = array();
737 foreach($response_header_lines as $header_line) {
738 list($header,$value) = explode(': ', $header_line, 2);
739 $response_header_array[$header] .= $value."\n";
742 $cookie_array = array();
743 $cookies = explode("\n", $response_header_array["Set-Cookie"]);
744 foreach($cookies as $this_cookie) { array_push($cookie_array, "Cookie: ".$this_cookie); }
746 return array("code" => $response_code, "header" => $response_header_array, "cookies" => $cookie_array, "body" => $response_body);
749 // Step 9: Congratulate the user
750 function congrats() {
751 $blog = (int) $_GET['blog'];
752 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>';
753 if ( count($this->import['blogs']) > 1 )
754 echo '<li>'.__('In case you haven\'t done it already, you can import the posts from your other blogs:'). $this->show_blogs() . '</li>';
755 if ( $n = count($this->import['blogs'][$blog]['newusers']) )
756 echo '<li>'.sprintf(__('Go to <a href="%s" target="%s">Authors & 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>';
757 echo '<li>'.__('For security, click the link below to reset this importer.').'</li>';
761 // Figures out what to do, then does it.
763 if ( isset($_POST['restart']) )
766 $options = get_option('blogger_importer');
768 if ( is_array($options) )
769 foreach ( $options as $key => $value )
770 $this->$key = $value;
772 if ( isset( $_REQUEST['blog'] ) ) {
773 $blog = is_array($_REQUEST['blog']) ? array_shift( array_keys( $_REQUEST['blog'] ) ) : $_REQUEST['blog'];
775 $result = $this->import_blog( $blog );
776 if ( is_wp_error( $result ) )
777 echo $result->get_error_message();
778 } elseif ( isset($_GET['token']) )
780 elseif ( $this->token && $this->token_is_valid() )
785 $saved = $this->save_vars();
787 if ( $saved && !isset($_GET['noheader']) ) {
788 $restart = __('Restart');
789 $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.');
790 $submit = __('Clear account information');
791 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>";
795 function save_vars() {
796 $vars = get_object_vars($this);
797 update_option( 'blogger_importer', $vars );
799 return !empty($vars);
802 function admin_head() {
804 <style type="text/css">
805 td { text-align: center; line-height: 2em;}
806 thead td { font-weight: bold; }
815 background-color: #83B4D8;
825 text-align: center !important;
831 function Blogger_Import() {
832 global $importer_started;
833 $importer_started = time();
834 if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) {
835 wp_enqueue_script('jquery');
836 add_action('admin_head', array(&$this, 'admin_head'));
841 $blogger_import = new Blogger_Import();
843 register_importer('blogger', __('Blogger'), __('Import posts, comments, and users from a Blogger blog'), array ($blogger_import, 'start'));
846 var $links = array();
847 var $categories = array();
852 var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights');
853 var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft','author');
858 var $ns_contexts = array();
859 var $ns_decls = array();
860 var $is_xhtml = false;
861 var $skipped_div = false;
865 function AtomParser() {
867 $this->entry = new AtomEntry();
868 $this->map_attrs_func = create_function('$k,$v', 'return "$k=\"$v\"";');
869 $this->map_xmlns_func = create_function('$p,$n', '$xd = "xmlns"; if(strlen($n[0])>0) $xd .= ":{$n[0]}"; return "{$xd}=\"{$n[1]}\"";');
872 function parse($xml) {
875 array_unshift($this->ns_contexts, array());
877 $parser = xml_parser_create_ns();
878 xml_set_object($parser, $this);
879 xml_set_element_handler($parser, "start_element", "end_element");
880 xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);
881 xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0);
882 xml_set_character_data_handler($parser, "cdata");
883 xml_set_default_handler($parser, "_default");
884 xml_set_start_namespace_decl_handler($parser, "start_ns");
885 xml_set_end_namespace_decl_handler($parser, "end_ns");
889 xml_parse($parser, $xml);
891 xml_parser_free($parser);
896 function start_element($parser, $name, $attrs) {
898 $tag = array_pop(split(":", $name));
900 array_unshift($this->ns_contexts, $this->ns_decls);
904 if(!empty($this->in_content)) {
905 $attrs_prefix = array();
907 // resolve prefixes for attributes
908 foreach($attrs as $key => $value) {
909 $attrs_prefix[$this->ns_to_prefix($key)] = $this->xml_escape($value);
911 $attrs_str = join(' ', array_map($this->map_attrs_func, array_keys($attrs_prefix), array_values($attrs_prefix)));
912 if(strlen($attrs_str) > 0) {
913 $attrs_str = " " . $attrs_str;
916 $xmlns_str = join(' ', array_map($this->map_xmlns_func, array_keys($this->ns_contexts[0]), array_values($this->ns_contexts[0])));
917 if(strlen($xmlns_str) > 0) {
918 $xmlns_str = " " . $xmlns_str;
921 // handle self-closing tags (case: a new child found right-away, no text node)
922 if(count($this->in_content) == 2) {
923 array_push($this->in_content, ">");
926 array_push($this->in_content, "<". $this->ns_to_prefix($name) ."{$xmlns_str}{$attrs_str}");
927 } else if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) {
928 $this->in_content = array();
929 $this->is_xhtml = $attrs['type'] == 'xhtml';
930 array_push($this->in_content, array($tag,$this->depth));
931 } else if($tag == 'link') {
932 array_push($this->entry->links, $attrs);
933 } else if($tag == 'category') {
934 array_push($this->entry->categories, $attrs['term']);
937 $this->ns_decls = array();
940 function end_element($parser, $name) {
942 $tag = array_pop(split(":", $name));
944 if(!empty($this->in_content)) {
945 if($this->in_content[0][0] == $tag &&
946 $this->in_content[0][1] == $this->depth) {
947 array_shift($this->in_content);
948 if($this->is_xhtml) {
949 $this->in_content = array_slice($this->in_content, 2, count($this->in_content)-3);
951 $this->entry->$tag = join('',$this->in_content);
952 $this->in_content = array();
954 $endtag = $this->ns_to_prefix($name);
955 if (strpos($this->in_content[count($this->in_content)-1], '<' . $endtag) !== false) {
956 array_push($this->in_content, "/>");
958 array_push($this->in_content, "</$endtag>");
963 array_shift($this->ns_contexts);
965 #print str_repeat(" ", $this->depth * $this->indent) . "end_element('$name')" ."\n";
970 function start_ns($parser, $prefix, $uri) {
971 #print str_repeat(" ", $this->depth * $this->indent) . "starting: " . $prefix . ":" . $uri . "\n";
972 array_push($this->ns_decls, array($prefix,$uri));
975 function end_ns($parser, $prefix) {
976 #print str_repeat(" ", $this->depth * $this->indent) . "ending: #" . $prefix . "#\n";
979 function cdata($parser, $data) {
980 #print str_repeat(" ", $this->depth * $this->indent) . "data: #" . $data . "#\n";
981 if(!empty($this->in_content)) {
982 // handle self-closing tags (case: text node found, need to close element started)
983 if (strpos($this->in_content[count($this->in_content)-1], '<') !== false) {
984 array_push($this->in_content, ">");
986 array_push($this->in_content, $this->xml_escape($data));
990 function _default($parser, $data) {
991 # when does this gets called?
995 function ns_to_prefix($qname) {
996 $components = split(":", $qname);
997 $name = array_pop($components);
999 if(!empty($components)) {
1000 $ns = join(":",$components);
1001 foreach($this->ns_contexts as $context) {
1002 foreach($context as $mapping) {
1003 if($mapping[1] == $ns && strlen($mapping[0]) > 0) {
1004 return "$mapping[0]:$name";
1012 function xml_escape($string)
1014 return str_replace(array('&','"',"'",'<','>'),
1015 array('&','"',''','<','>'),