source: trunk/www.guidonia.net/wp/wp-admin/import/blogger.php@ 44

Last change on this file since 44 was 44, checked in by luciano, 14 years ago
File size: 36.9 KB
Line 
1<?php
2/**
3 * Blogger Importer
4 *
5 * @package WordPress
6 * @subpackage Importer
7 */
8
9/**
10 * How many records per GData query
11 *
12 * @package WordPress
13 * @subpackage Blogger_Import
14 * @var int
15 * @since unknown
16 */
17define( 'MAX_RESULTS', 50 );
18
19/**
20 * How many seconds to let the script run
21 *
22 * @package WordPress
23 * @subpackage Blogger_Import
24 * @var int
25 * @since unknown
26 */
27define( 'MAX_EXECUTION_TIME', 20 );
28
29/**
30 * How many seconds between status bar updates
31 *
32 * @package WordPress
33 * @subpackage Blogger_Import
34 * @var int
35 * @since unknown
36 */
37define( 'STATUS_INTERVAL', 3 );
38
39/**
40 * Blogger Importer class
41 *
42 * @since unknown
43 */
44class Blogger_Import {
45
46 // Shows the welcome screen and the magic auth link.
47 function greet() {
48 $next_url = get_option('siteurl') . '/wp-admin/index.php?import=blogger&amp;noheader=true';
49 $auth_url = "https://www.google.com/accounts/AuthSubRequest";
50 $title = __('Import Blogger');
51 $welcome = __('Howdy! This importer allows you to import posts and comments from your Blogger account into your WordPress blog.');
52 $prereqs = __('To use this importer, you must have a Google account and an upgraded (New, was Beta) blog hosted on blogspot.com or a custom domain (not FTP).');
53 $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.');
54 $auth = esc_attr__('Authorize');
55
56 echo "
57 <div class='wrap'>
58 ".screen_icon()."
59 <h2>$title</h2>
60 <p>$welcome</p><p>$prereqs</p><p>$stepone</p>
61 <form action='$auth_url' method='get'>
62 <p class='submit' style='text-align:left;'>
63 <input type='submit' class='button' value='$auth' />
64 <input type='hidden' name='scope' value='http://www.blogger.com/feeds/' />
65 <input type='hidden' name='session' value='1' />
66 <input type='hidden' name='secure' value='0' />
67 <input type='hidden' name='next' value='$next_url' />
68 </p>
69 </form>
70 </div>\n";
71 }
72
73 function uh_oh($title, $message, $info) {
74 echo "<div class='wrap'>";
75 screen_icon();
76 echo "<h2>$title</h2><p>$message</p><pre>$info</pre></div>";
77 }
78
79 function auth() {
80 // We have a single-use token that must be upgraded to a session token.
81 $token = preg_replace( '/[^-_0-9a-zA-Z]/', '', $_GET['token'] );
82 $headers = array(
83 "GET /accounts/AuthSubSessionToken HTTP/1.0",
84 "Authorization: AuthSub token=\"$token\""
85 );
86 $request = join( "\r\n", $headers ) . "\r\n\r\n";
87 $sock = $this->_get_auth_sock( );
88 if ( ! $sock ) return false;
89 $response = $this->_txrx( $sock, $request );
90 preg_match( '/token=([-_0-9a-z]+)/i', $response, $matches );
91 if ( empty( $matches[1] ) ) {
92 $this->uh_oh(
93 __( 'Authorization failed' ),
94 __( 'Something went wrong. If the problem persists, send this info to support:' ),
95 htmlspecialchars($response)
96 );
97 return false;
98 }
99 $this->token = $matches[1];
100
101 wp_redirect( remove_query_arg( array( 'token', 'noheader' ) ) );
102 }
103
104 function get_token_info() {
105 $headers = array(
106 "GET /accounts/AuthSubTokenInfo HTTP/1.0",
107 "Authorization: AuthSub token=\"$this->token\""
108 );
109 $request = join( "\r\n", $headers ) . "\r\n\r\n";
110 $sock = $this->_get_auth_sock( );
111 if ( ! $sock ) return;
112 $response = $this->_txrx( $sock, $request );
113 return $this->parse_response($response);
114 }
115
116 function token_is_valid() {
117 $info = $this->get_token_info();
118
119 if ( $info['code'] == 200 )
120 return true;
121
122 return false;
123 }
124
125 function show_blogs($iter = 0) {
126 if ( empty($this->blogs) ) {
127 $headers = array(
128 "GET /feeds/default/blogs HTTP/1.0",
129 "Host: www.blogger.com",
130 "Authorization: AuthSub token=\"$this->token\""
131 );
132 $request = join( "\r\n", $headers ) . "\r\n\r\n";
133 $sock = $this->_get_blogger_sock( );
134 if ( ! $sock ) return;
135 $response = $this->_txrx( $sock, $request );
136
137 // Quick and dirty XML mining.
138 list( $headers, $xml ) = explode( "\r\n\r\n", $response );
139 $p = xml_parser_create();
140 xml_parse_into_struct($p, $xml, $vals, $index);
141 xml_parser_free($p);
142
143 $this->title = $vals[$index['TITLE'][0]]['value'];
144
145 // Give it a few retries... this step often flakes out the first time.
146 if ( empty( $index['ENTRY'] ) ) {
147 if ( $iter < 3 ) {
148 return $this->show_blogs($iter + 1);
149 } else {
150 $this->uh_oh(
151 __('Trouble signing in'),
152 __('We were not able to gain access to your account. Try starting over.'),
153 ''
154 );
155 return false;
156 }
157 }
158
159 foreach ( $index['ENTRY'] as $i ) {
160 $blog = array();
161 while ( ( $tag = $vals[$i] ) && ! ( $tag['tag'] == 'ENTRY' && $tag['type'] == 'close' ) ) {
162 if ( $tag['tag'] == 'TITLE' ) {
163 $blog['title'] = $tag['value'];
164 } elseif ( $tag['tag'] == 'SUMMARY' ) {
165 $blog['summary'] == $tag['value'];
166 } elseif ( $tag['tag'] == 'LINK' ) {
167 if ( $tag['attributes']['REL'] == 'alternate' && $tag['attributes']['TYPE'] == 'text/html' ) {
168 $parts = parse_url( $tag['attributes']['HREF'] );
169 $blog['host'] = $parts['host'];
170 } elseif ( $tag['attributes']['REL'] == 'edit' )
171 $blog['gateway'] = $tag['attributes']['HREF'];
172 }
173 ++$i;
174 }
175 if ( ! empty ( $blog ) ) {
176 $blog['total_posts'] = $this->get_total_results('posts', $blog['host']);
177 $blog['total_comments'] = $this->get_total_results('comments', $blog['host']);
178 $blog['mode'] = 'init';
179 $this->blogs[] = $blog;
180 }
181 }
182
183 if ( empty( $this->blogs ) ) {
184 $this->uh_oh(
185 __('No blogs found'),
186 __('We were able to log in but there were no blogs. Try a different account next time.'),
187 ''
188 );
189 return false;
190 }
191 }
192//echo '<pre>'.print_r($this,1).'</pre>';
193 $start = esc_js( __('Import') );
194 $continue = esc_js( __('Continue') );
195 $stop = esc_js( __('Importing...') );
196 $authors = esc_js( __('Set Authors') );
197 $loadauth = esc_js( __('Preparing author mapping form...') );
198 $authhead = esc_js( __('Final Step: Author Mapping') );
199 $nothing = esc_js( __('Nothing was imported. Had you already imported this blog?') );
200 $title = __('Blogger Blogs');
201 $name = __('Blog Name');
202 $url = __('Blog URL');
203 $action = __('The Magic Button');
204 $posts = __('Posts');
205 $comments = __('Comments');
206 $noscript = __('This feature requires Javascript but it seems to be disabled. Please enable Javascript and then reload this page. Don&#8217;t worry, you can turn it back off when you&#8217;re done.');
207
208 $interval = STATUS_INTERVAL * 1000;
209
210 foreach ( $this->blogs as $i => $blog ) {
211 if ( $blog['mode'] == 'init' )
212 $value = $start;
213 elseif ( $blog['mode'] == 'posts' || $blog['mode'] == 'comments' )
214 $value = $continue;
215 else
216 $value = $authors;
217 $value = esc_attr($value);
218 $blogtitle = esc_js( $blog['title'] );
219 $pdone = isset($blog['posts_done']) ? (int) $blog['posts_done'] : 0;
220 $cdone = isset($blog['comments_done']) ? (int) $blog['comments_done'] : 0;
221 $init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}'," . $this->get_js_status($i) . ');';
222 $pstat = "<div class='ind' id='pind$i'>&nbsp;</div><div id='pstat$i' class='stat'>$pdone/{$blog['total_posts']}</div>";
223 $cstat = "<div class='ind' id='cind$i'>&nbsp;</div><div id='cstat$i' class='stat'>$cdone/{$blog['total_comments']}</div>";
224 $rows .= "<tr id='blog$i'><td class='blogtitle'>$blogtitle</td><td class='bloghost'>{$blog['host']}</td><td class='bar'>$pstat</td><td class='bar'>$cstat</td><td class='submit'><input type='submit' class='button' id='submit$i' value='$value' /><input type='hidden' name='blog' value='$i' /></td></tr>\n";
225 }
226
227 echo "<div class='wrap'><h2>$title</h2><noscript>$noscript</noscript><table cellpadding='5px'><thead><tr><td>$name</td><td>$url</td><td>$posts</td><td>$comments</td><td>$action</td></tr></thead>\n$rows</table></div>";
228 echo "
229 <script type='text/javascript'>
230 /* <![CDATA[ */
231 var strings = {cont:'$continue',stop:'$stop',stopping:'$stopping',authors:'$authors',nothing:'$nothing'};
232 var blogs = {};
233 function blog(i, title, mode, status){
234 this.blog = i;
235 this.mode = mode;
236 this.title = title;
237 this.status = status;
238 this.button = document.getElementById('submit'+this.blog);
239 };
240 blog.prototype = {
241 start: function() {
242 this.cont = true;
243 this.kick();
244 this.check();
245 },
246 kick: function() {
247 ++this.kicks;
248 var i = this.blog;
249 jQuery.post('admin.php?import=blogger&noheader=true',{blog:this.blog},function(text,result){blogs[i].kickd(text,result)});
250 },
251 check: function() {
252 ++this.checks;
253 var i = this.blog;
254 jQuery.post('admin.php?import=blogger&noheader=true&status=true',{blog:this.blog},function(text,result){blogs[i].checkd(text,result)});
255 },
256 kickd: function(text, result) {
257 if ( result == 'error' ) {
258 // TODO: exception handling
259 if ( this.cont )
260 setTimeout('blogs['+this.blog+'].kick()', 1000);
261 } else {
262 if ( text == 'done' ) {
263 this.stop();
264 this.done();
265 } else if ( text == 'nothing' ) {
266 this.stop();
267 this.nothing();
268 } else if ( text == 'continue' ) {
269 this.kick();
270 } else if ( this.mode = 'stopped' )
271 jQuery(this.button).attr('value', strings.cont);
272 }
273 --this.kicks;
274 },
275 checkd: function(text, result) {
276 if ( result == 'error' ) {
277 // TODO: exception handling
278 } else {
279 eval('this.status='+text);
280 jQuery('#pstat'+this.blog).empty().append(this.status.p1+'/'+this.status.p2);
281 jQuery('#cstat'+this.blog).empty().append(this.status.c1+'/'+this.status.c2);
282 this.update();
283 if ( this.cont || this.kicks > 0 )
284 setTimeout('blogs['+this.blog+'].check()', $interval);
285 }
286 --this.checks;
287 },
288 update: function() {
289 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');
290 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');
291 },
292 stop: function() {
293 this.cont = false;
294 },
295 done: function() {
296 this.mode = 'authors';
297 jQuery(this.button).attr('value', strings.authors);
298 },
299 nothing: function() {
300 this.mode = 'nothing';
301 jQuery(this.button).remove();
302 alert(strings.nothing);
303 },
304 getauthors: function() {
305 if ( jQuery('div.wrap').length > 1 )
306 jQuery('div.wrap').gt(0).remove();
307 jQuery('div.wrap').empty().append('<h2>$authhead</h2><h3>' + this.title + '</h3>');
308 jQuery('div.wrap').append('<p id=\"auth\">$loadauth</p>');
309 jQuery('p#auth').load('index.php?import=blogger&noheader=true&authors=1',{blog:this.blog});
310 },
311 init: function() {
312 this.update();
313 var i = this.blog;
314 jQuery(this.button).bind('click', function(){return blogs[i].click();});
315 this.kicks = 0;
316 this.checks = 0;
317 },
318 click: function() {
319 if ( this.mode == 'init' || this.mode == 'stopped' || this.mode == 'posts' || this.mode == 'comments' ) {
320 this.mode = 'started';
321 this.start();
322 jQuery(this.button).attr('value', strings.stop);
323 } else if ( this.mode == 'started' ) {
324 return false; // let it run...
325 this.mode = 'stopped';
326 this.stop();
327 if ( this.checks > 0 || this.kicks > 0 ) {
328 this.mode = 'stopping';
329 jQuery(this.button).attr('value', strings.stopping);
330 } else {
331 jQuery(this.button).attr('value', strings.cont);
332 }
333 } else if ( this.mode == 'authors' ) {
334 document.location = 'index.php?import=blogger&authors=1&blog='+this.blog;
335 //this.mode = 'authors2';
336 //this.getauthors();
337 }
338 return false;
339 }
340 };
341 $init
342 jQuery.each(blogs, function(i, me){me.init();});
343 /* ]]> */
344 </script>\n";
345 }
346
347 // Handy function for stopping the script after a number of seconds.
348 function have_time() {
349 global $importer_started;
350 if ( time() - $importer_started > MAX_EXECUTION_TIME )
351 die('continue');
352 return true;
353 }
354
355 function get_total_results($type, $host) {
356 $headers = array(
357 "GET /feeds/$type/default?max-results=1&start-index=2 HTTP/1.0",
358 "Host: $host",
359 "Authorization: AuthSub token=\"$this->token\""
360 );
361 $request = join( "\r\n", $headers ) . "\r\n\r\n";
362 $sock = $this->_get_blogger_sock( $host );
363 if ( ! $sock ) return;
364 $response = $this->_txrx( $sock, $request );
365 $response = $this->parse_response( $response );
366 $parser = xml_parser_create();
367 xml_parse_into_struct($parser, $response['body'], $struct, $index);
368 xml_parser_free($parser);
369 $total_results = $struct[$index['OPENSEARCH:TOTALRESULTS'][0]]['value'];
370 return (int) $total_results;
371 }
372
373 function import_blog($blogID) {
374 global $importing_blog;
375 $importing_blog = $blogID;
376
377 if ( isset($_GET['authors']) )
378 return print($this->get_author_form());
379
380 header('Content-Type: text/plain');
381
382 if ( isset($_GET['status']) )
383 die($this->get_js_status());
384
385 if ( isset($_GET['saveauthors']) )
386 die($this->save_authors());
387
388 $blog = $this->blogs[$blogID];
389 $total_results = $this->get_total_results('posts', $blog['host']);
390 $this->blogs[$importing_blog]['total_posts'] = $total_results;
391
392 $start_index = $total_results - MAX_RESULTS + 1;
393
394 if ( isset( $this->blogs[$importing_blog]['posts_start_index'] ) )
395 $start_index = (int) $this->blogs[$importing_blog]['posts_start_index'];
396 elseif ( $total_results > MAX_RESULTS )
397 $start_index = $total_results - MAX_RESULTS + 1;
398 else
399 $start_index = 1;
400
401 // This will be positive until we have finished importing posts
402 if ( $start_index > 0 ) {
403 // Grab all the posts
404 $this->blogs[$importing_blog]['mode'] = 'posts';
405 $query = "start-index=$start_index&max-results=" . MAX_RESULTS;
406 do {
407 $index = $struct = $entries = array();
408 $headers = array(
409 "GET /feeds/posts/default?$query HTTP/1.0",
410 "Host: {$blog['host']}",
411 "Authorization: AuthSub token=\"$this->token\""
412 );
413 $request = join( "\r\n", $headers ) . "\r\n\r\n";
414 $sock = $this->_get_blogger_sock( $blog['host'] );
415 if ( ! $sock ) return; // TODO: Error handling
416 $response = $this->_txrx( $sock, $request );
417
418 $response = $this->parse_response( $response );
419
420 // Extract the entries and send for insertion
421 preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches );
422 if ( count( $matches[0] ) ) {
423 $entries = array_reverse($matches[0]);
424 foreach ( $entries as $entry ) {
425 $entry = "<feed>$entry</feed>";
426 $AtomParser = new AtomParser();
427 $AtomParser->parse( $entry );
428 $result = $this->import_post($AtomParser->entry);
429 if ( is_wp_error( $result ) )
430 return $result;
431 unset($AtomParser);
432 }
433 } else break;
434
435 // Get the 'previous' query string which we'll use on the next iteration
436 $query = '';
437 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches);
438 if ( count( $matches[1] ) )
439 foreach ( $matches[1] as $match )
440 if ( preg_match('/rel=.previous./', $match) )
441 $query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') );
442
443 if ( $query ) {
444 parse_str($query, $q);
445 $this->blogs[$importing_blog]['posts_start_index'] = (int) $q['start-index'];
446 } else
447 $this->blogs[$importing_blog]['posts_start_index'] = 0;
448 $this->save_vars();
449 } while ( !empty( $query ) && $this->have_time() );
450 }
451
452 $total_results = $this->get_total_results( 'comments', $blog['host'] );
453 $this->blogs[$importing_blog]['total_comments'] = $total_results;
454
455 if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) )
456 $start_index = (int) $this->blogs[$importing_blog]['comments_start_index'];
457 elseif ( $total_results > MAX_RESULTS )
458 $start_index = $total_results - MAX_RESULTS + 1;
459 else
460 $start_index = 1;
461
462 if ( $start_index > 0 ) {
463 // Grab all the comments
464 $this->blogs[$importing_blog]['mode'] = 'comments';
465 $query = "start-index=$start_index&max-results=" . MAX_RESULTS;
466 do {
467 $index = $struct = $entries = array();
468 $headers = array(
469 "GET /feeds/comments/default?$query HTTP/1.0",
470 "Host: {$blog['host']}",
471 "Authorization: AuthSub token=\"$this->token\""
472 );
473 $request = join( "\r\n", $headers ) . "\r\n\r\n";
474 $sock = $this->_get_blogger_sock( $blog['host'] );
475 if ( ! $sock ) return; // TODO: Error handling
476 $response = $this->_txrx( $sock, $request );
477
478 $response = $this->parse_response( $response );
479
480 // Extract the comments and send for insertion
481 preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches );
482 if ( count( $matches[0] ) ) {
483 $entries = array_reverse( $matches[0] );
484 foreach ( $entries as $entry ) {
485 $entry = "<feed>$entry</feed>";
486 $AtomParser = new AtomParser();
487 $AtomParser->parse( $entry );
488 $this->import_comment($AtomParser->entry);
489 unset($AtomParser);
490 }
491 }
492
493 // Get the 'previous' query string which we'll use on the next iteration
494 $query = '';
495 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches);
496 if ( count( $matches[1] ) )
497 foreach ( $matches[1] as $match )
498 if ( preg_match('/rel=.previous./', $match) )
499 $query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') );
500
501 parse_str($query, $q);
502
503 $this->blogs[$importing_blog]['comments_start_index'] = (int) $q['start-index'];
504 $this->save_vars();
505 } while ( !empty( $query ) && $this->have_time() );
506 }
507 $this->blogs[$importing_blog]['mode'] = 'authors';
508 $this->save_vars();
509 if ( !$this->blogs[$importing_blog]['posts_done'] && !$this->blogs[$importing_blog]['comments_done'] )
510 die('nothing');
511 do_action('import_done', 'blogger');
512 die('done');
513 }
514
515 function convert_date( $date ) {
516 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);
517 $offset = iso8601_timezone_to_offset( $date_bits[7] );
518 $timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]);
519 $timestamp -= $offset; // Convert from Blogger local time to GMT
520 $timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time
521 return gmdate('Y-m-d H:i:s', $timestamp);
522 }
523
524 function no_apos( $string ) {
525 return str_replace( '&apos;', "'", $string);
526 }
527
528 function min_whitespace( $string ) {
529 return preg_replace( '|\s+|', ' ', $string );
530 }
531
532 function import_post( $entry ) {
533 global $importing_blog;
534
535 // The old permalink is all Blogger gives us to link comments to their posts.
536 if ( isset( $entry->draft ) )
537 $rel = 'self';
538 else
539 $rel = 'alternate';
540 foreach ( $entry->links as $link ) {
541 if ( $link['rel'] == $rel ) {
542 $parts = parse_url( $link['href'] );
543 $entry->old_permalink = $parts['path'];
544 break;
545 }
546 }
547
548 $post_date = $this->convert_date( $entry->published );
549 $post_content = trim( addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) ) );
550 $post_title = trim( addslashes( $this->no_apos( $this->min_whitespace( $entry->title ) ) ) );
551 $post_status = isset( $entry->draft ) ? 'draft' : 'publish';
552
553 // Clean up content
554 $post_content = preg_replace_callback('|<(/?[A-Z]+)|', create_function('$match', 'return "<" . strtolower($match[1]);'), $post_content);
555 $post_content = str_replace('<br>', '<br />', $post_content);
556 $post_content = str_replace('<hr>', '<hr />', $post_content);
557
558 // Checks for duplicates
559 if ( isset( $this->blogs[$importing_blog]['posts'][$entry->old_permalink] ) ) {
560 ++$this->blogs[$importing_blog]['posts_skipped'];
561 } elseif ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) {
562 $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
563 ++$this->blogs[$importing_blog]['posts_skipped'];
564 } else {
565 $post = compact('post_date', 'post_content', 'post_title', 'post_status');
566
567 $post_id = wp_insert_post($post);
568 if ( is_wp_error( $post_id ) )
569 return $post_id;
570
571 wp_create_categories( array_map( 'addslashes', $entry->categories ), $post_id );
572
573 $author = $this->no_apos( strip_tags( $entry->author ) );
574
575 add_post_meta( $post_id, 'blogger_blog', $this->blogs[$importing_blog]['host'], true );
576 add_post_meta( $post_id, 'blogger_author', $author, true );
577 add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true );
578
579 $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
580 ++$this->blogs[$importing_blog]['posts_done'];
581 }
582 $this->save_vars();
583 return;
584 }
585
586 function import_comment( $entry ) {
587 global $importing_blog;
588
589 // Drop the #fragment and we have the comment's old post permalink.
590 foreach ( $entry->links as $link ) {
591 if ( $link['rel'] == 'alternate' ) {
592 $parts = parse_url( $link['href'] );
593 $entry->old_permalink = $parts['fragment'];
594 $entry->old_post_permalink = $parts['path'];
595 break;
596 }
597 }
598
599 $comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink];
600 preg_match('#<name>(.+?)</name>.*(?:\<uri>(.+?)</uri>)?#', $entry->author, $matches);
601 $comment_author = addslashes( $this->no_apos( strip_tags( (string) $matches[1] ) ) );
602 $comment_author_url = addslashes( $this->no_apos( strip_tags( (string) $matches[2] ) ) );
603 $comment_date = $this->convert_date( $entry->updated );
604 $comment_content = addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) );
605
606 // Clean up content
607 $comment_content = preg_replace_callback('|<(/?[A-Z]+)|', create_function('$match', 'return "<" . strtolower($match[1]);'), $comment_content);
608 $comment_content = str_replace('<br>', '<br />', $comment_content);
609 $comment_content = str_replace('<hr>', '<hr />', $comment_content);
610
611 // Checks for duplicates
612 if (
613 isset( $this->blogs[$importing_blog]['comments'][$entry->old_permalink] ) ||
614 comment_exists( $comment_author, $comment_date )
615 ) {
616 ++$this->blogs[$importing_blog]['comments_skipped'];
617 } else {
618 $comment = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_date', 'comment_content');
619
620 $comment_id = wp_insert_comment($comment);
621
622 $this->blogs[$importing_blog]['comments'][$entry->old_permalink] = $comment_id;
623
624 ++$this->blogs[$importing_blog]['comments_done'];
625 }
626 $this->save_vars();
627 }
628
629 function get_js_status($blog = false) {
630 global $importing_blog;
631 if ( $blog === false )
632 $blog = $this->blogs[$importing_blog];
633 else
634 $blog = $this->blogs[$blog];
635 $p1 = isset( $blog['posts_done'] ) ? (int) $blog['posts_done'] : 0;
636 $p2 = isset( $blog['total_posts'] ) ? (int) $blog['total_posts'] : 0;
637 $c1 = isset( $blog['comments_done'] ) ? (int) $blog['comments_done'] : 0;
638 $c2 = isset( $blog['total_comments'] ) ? (int) $blog['total_comments'] : 0;
639 return "{p1:$p1,p2:$p2,c1:$c1,c2:$c2}";
640 }
641
642 function get_author_form($blog = false) {
643 global $importing_blog, $wpdb, $current_user;
644 if ( $blog === false )
645 $blog = & $this->blogs[$importing_blog];
646 else
647 $blog = & $this->blogs[$blog];
648
649 if ( !isset( $blog['authors'] ) ) {
650 $post_ids = array_values($blog['posts']);
651 $authors = (array) $wpdb->get_col("SELECT DISTINCT meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN (" . join( ',', $post_ids ) . ")");
652 $blog['authors'] = array_map(null, $authors, array_fill(0, count($authors), $current_user->ID));
653 $this->save_vars();
654 }
655
656 $directions = __('All posts were imported with the current user as author. Use this form to move each Blogger user&#8217;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 &#8220;Restart&#8221; function below.');
657 $heading = __('Author mapping');
658 $blogtitle = "{$blog['title']} ({$blog['host']})";
659 $mapthis = __('Blogger username');
660 $tothis = __('WordPress login');
661 $submit = esc_js( __('Save Changes') );
662
663 foreach ( $blog['authors'] as $i => $author )
664 $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>";
665
666 return "<div class='wrap'><h2>$heading</h2><h3>$blogtitle</h3><p>$directions</p><form action='index.php?import=blogger&amp;noheader=true&saveauthors=1' method='post'><input type='hidden' name='blog' value='" . esc_attr($importing_blog) . "' /><table cellpadding='5'><thead><td>$mapthis</td><td>$tothis</td></thead>$rows<tr><td></td><td class='submit'><input type='submit' class='button authorsubmit' value='$submit' /></td></tr></table></form></div>";
667 }
668
669 function get_user_options($current) {
670 global $importer_users;
671 if ( ! isset( $importer_users ) )
672 $importer_users = (array) get_users_of_blog();
673
674 foreach ( $importer_users as $user ) {
675 $sel = ( $user->user_id == $current ) ? " selected='selected'" : '';
676 $options .= "<option value='$user->user_id'$sel>$user->display_name</option>";
677 }
678
679 return $options;
680 }
681
682 function save_authors() {
683 global $importing_blog, $wpdb;
684 $authors = (array) $_POST['authors'];
685
686 $host = $this->blogs[$importing_blog]['host'];
687
688 // Get an array of posts => authors
689 $post_ids = (array) $wpdb->get_col( $wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'blogger_blog' AND meta_value = %s", $host) );
690 $post_ids = join( ',', $post_ids );
691 $results = (array) $wpdb->get_results("SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN ($post_ids)");
692 foreach ( $results as $row )
693 $authors_posts[$row->post_id] = $row->meta_value;
694
695 foreach ( $authors as $author => $user_id ) {
696 $user_id = (int) $user_id;
697
698 // Skip authors that haven't been changed
699 if ( $user_id == $this->blogs[$importing_blog]['authors'][$author][1] )
700 continue;
701
702 // Get a list of the selected author's posts
703 $post_ids = (array) array_keys( $authors_posts, $this->blogs[$importing_blog]['authors'][$author][0] );
704 $post_ids = join( ',', $post_ids);
705
706 $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET post_author = %d WHERE id IN ($post_ids)", $user_id) );
707 $this->blogs[$importing_blog]['authors'][$author][1] = $user_id;
708 }
709 $this->save_vars();
710
711 wp_redirect('edit.php');
712 }
713
714 function _get_auth_sock() {
715 // Connect to https://www.google.com
716 if ( !$sock = @ fsockopen('ssl://www.google.com', 443, $errno, $errstr) ) {
717 $this->uh_oh(
718 __('Could not connect to https://www.google.com'),
719 __('There was a problem opening a secure connection to Google. This is what went wrong:'),
720 "$errstr ($errno)"
721 );
722 return false;
723 }
724 return $sock;
725 }
726
727 function _get_blogger_sock($host = 'www2.blogger.com') {
728 if ( !$sock = @ fsockopen($host, 80, $errno, $errstr) ) {
729 $this->uh_oh(
730 sprintf( __('Could not connect to %s'), $host ),
731 __('There was a problem opening a connection to Blogger. This is what went wrong:'),
732 "$errstr ($errno)"
733 );
734 return false;
735 }
736 return $sock;
737 }
738
739 function _txrx( $sock, $request ) {
740 fwrite( $sock, $request );
741 while ( ! feof( $sock ) )
742 $response .= @ fread ( $sock, 8192 );
743 fclose( $sock );
744 return $response;
745 }
746
747 function revoke($token) {
748 $headers = array(
749 "GET /accounts/AuthSubRevokeToken HTTP/1.0",
750 "Authorization: AuthSub token=\"$token\""
751 );
752 $request = join( "\r\n", $headers ) . "\r\n\r\n";
753 $sock = $this->_get_auth_sock( );
754 if ( ! $sock ) return false;
755 $this->_txrx( $sock, $request );
756 }
757
758 function restart() {
759 global $wpdb;
760 $options = get_option( 'blogger_importer' );
761
762 if ( isset( $options['token'] ) )
763 $this->revoke( $options['token'] );
764
765 delete_option('blogger_importer');
766 $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'blogger_author'");
767 wp_redirect('?import=blogger');
768 }
769
770 // Returns associative array of code, header, cookies, body. Based on code from php.net.
771 function parse_response($this_response) {
772 // Split response into header and body sections
773 list($response_headers, $response_body) = explode("\r\n\r\n", $this_response, 2);
774 $response_header_lines = explode("\r\n", $response_headers);
775
776 // First line of headers is the HTTP response code
777 $http_response_line = array_shift($response_header_lines);
778 if(preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@',$http_response_line, $matches)) { $response_code = $matches[1]; }
779
780 // put the rest of the headers in an array
781 $response_header_array = array();
782 foreach($response_header_lines as $header_line) {
783 list($header,$value) = explode(': ', $header_line, 2);
784 $response_header_array[$header] .= $value."\n";
785 }
786
787 $cookie_array = array();
788 $cookies = explode("\n", $response_header_array["Set-Cookie"]);
789 foreach($cookies as $this_cookie) { array_push($cookie_array, "Cookie: ".$this_cookie); }
790
791 return array("code" => $response_code, "header" => $response_header_array, "cookies" => $cookie_array, "body" => $response_body);
792 }
793
794 // Step 9: Congratulate the user
795 function congrats() {
796 $blog = (int) $_GET['blog'];
797 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>';
798 if ( count($this->import['blogs']) > 1 )
799 echo '<li>'.__('In case you haven&#8217;t done it already, you can import the posts from your other blogs:'). $this->show_blogs() . '</li>';
800 if ( $n = count($this->import['blogs'][$blog]['newusers']) )
801 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>';
802 echo '<li>'.__('For security, click the link below to reset this importer.').'</li>';
803 echo '</ul>';
804 }
805
806 // Figures out what to do, then does it.
807 function start() {
808 if ( isset($_POST['restart']) )
809 $this->restart();
810
811 $options = get_option('blogger_importer');
812
813 if ( is_array($options) )
814 foreach ( $options as $key => $value )
815 $this->$key = $value;
816
817 if ( isset( $_REQUEST['blog'] ) ) {
818 $blog = is_array($_REQUEST['blog']) ? array_shift( $keys = array_keys( $_REQUEST['blog'] ) ) : $_REQUEST['blog'];
819 $blog = (int) $blog;
820 $result = $this->import_blog( $blog );
821 if ( is_wp_error( $result ) )
822 echo $result->get_error_message();
823 } elseif ( isset($_GET['token']) )
824 $this->auth();
825 elseif ( isset($this->token) && $this->token_is_valid() )
826 $this->show_blogs();
827 else
828 $this->greet();
829
830 $saved = $this->save_vars();
831
832 if ( $saved && !isset($_GET['noheader']) ) {
833 $restart = __('Restart');
834 $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.');
835 $submit = esc_attr__('Clear account information');
836 echo "<div class='wrap'><h2>$restart</h2><p>$message</p><form method='post' action='?import=blogger&amp;noheader=true'><p class='submit' style='text-align:left;'><input type='submit' class='button' value='$submit' name='restart' /></p></form></div>";
837 }
838 }
839
840 function save_vars() {
841 $vars = get_object_vars($this);
842 update_option( 'blogger_importer', $vars );
843
844 return !empty($vars);
845 }
846
847 function admin_head() {
848?>
849<style type="text/css">
850td { text-align: center; line-height: 2em;}
851thead td { font-weight: bold; }
852.bar {
853 width: 200px;
854 text-align: left;
855 line-height: 2em;
856 padding: 0px;
857}
858.ind {
859 position: absolute;
860 background-color: #83B4D8;
861 width: 1px;
862 z-index: 9;
863}
864.stat {
865 z-index: 10;
866 position: relative;
867 text-align: center;
868}
869</style>
870<?php
871 }
872
873 function Blogger_Import() {
874 global $importer_started;
875 $importer_started = time();
876 if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) {
877 wp_enqueue_script('jquery');
878 add_action('admin_head', array(&$this, 'admin_head'));
879 }
880 }
881}
882
883$blogger_import = new Blogger_Import();
884
885register_importer('blogger', __('Blogger'), __('Import posts, comments, and users from a Blogger blog.'), array ($blogger_import, 'start'));
886
887class AtomEntry {
888 var $links = array();
889 var $categories = array();
890}
891
892class AtomParser {
893
894 var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights');
895 var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft','author');
896
897 var $depth = 0;
898 var $indent = 2;
899 var $in_content;
900 var $ns_contexts = array();
901 var $ns_decls = array();
902 var $is_xhtml = false;
903 var $skipped_div = false;
904
905 var $entry;
906
907 function AtomParser() {
908
909 $this->entry = new AtomEntry();
910 $this->map_attrs_func = create_function('$k,$v', 'return "$k=\"$v\"";');
911 $this->map_xmlns_func = create_function('$p,$n', '$xd = "xmlns"; if(strlen($n[0])>0) $xd .= ":{$n[0]}"; return "{$xd}=\"{$n[1]}\"";');
912 }
913
914 function parse($xml) {
915
916 global $app_logging;
917 array_unshift($this->ns_contexts, array());
918
919 $parser = xml_parser_create_ns();
920 xml_set_object($parser, $this);
921 xml_set_element_handler($parser, "start_element", "end_element");
922 xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);
923 xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0);
924 xml_set_character_data_handler($parser, "cdata");
925 xml_set_default_handler($parser, "_default");
926 xml_set_start_namespace_decl_handler($parser, "start_ns");
927 xml_set_end_namespace_decl_handler($parser, "end_ns");
928
929 $contents = "";
930
931 xml_parse($parser, $xml);
932
933 xml_parser_free($parser);
934
935 return true;
936 }
937
938 function start_element($parser, $name, $attrs) {
939
940 $tag = array_pop(split(":", $name));
941
942 array_unshift($this->ns_contexts, $this->ns_decls);
943
944 $this->depth++;
945
946 if(!empty($this->in_content)) {
947 $attrs_prefix = array();
948
949 // resolve prefixes for attributes
950 foreach($attrs as $key => $value) {
951 $attrs_prefix[$this->ns_to_prefix($key)] = $this->xml_escape($value);
952 }
953 $attrs_str = join(' ', array_map($this->map_attrs_func, array_keys($attrs_prefix), array_values($attrs_prefix)));
954 if(strlen($attrs_str) > 0) {
955 $attrs_str = " " . $attrs_str;
956 }
957
958 $xmlns_str = join(' ', array_map($this->map_xmlns_func, array_keys($this->ns_contexts[0]), array_values($this->ns_contexts[0])));
959 if(strlen($xmlns_str) > 0) {
960 $xmlns_str = " " . $xmlns_str;
961 }
962
963 // handle self-closing tags (case: a new child found right-away, no text node)
964 if(count($this->in_content) == 2) {
965 array_push($this->in_content, ">");
966 }
967
968 array_push($this->in_content, "<". $this->ns_to_prefix($name) ."{$xmlns_str}{$attrs_str}");
969 } else if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) {
970 $this->in_content = array();
971 $this->is_xhtml = $attrs['type'] == 'xhtml';
972 array_push($this->in_content, array($tag,$this->depth));
973 } else if($tag == 'link') {
974 array_push($this->entry->links, $attrs);
975 } else if($tag == 'category') {
976 array_push($this->entry->categories, $attrs['term']);
977 }
978
979 $this->ns_decls = array();
980 }
981
982 function end_element($parser, $name) {
983
984 $tag = array_pop(split(":", $name));
985
986 if(!empty($this->in_content)) {
987 if($this->in_content[0][0] == $tag &&
988 $this->in_content[0][1] == $this->depth) {
989 array_shift($this->in_content);
990 if($this->is_xhtml) {
991 $this->in_content = array_slice($this->in_content, 2, count($this->in_content)-3);
992 }
993 $this->entry->$tag = join('',$this->in_content);
994 $this->in_content = array();
995 } else {
996 $endtag = $this->ns_to_prefix($name);
997 if (strpos($this->in_content[count($this->in_content)-1], '<' . $endtag) !== false) {
998 array_push($this->in_content, "/>");
999 } else {
1000 array_push($this->in_content, "</$endtag>");
1001 }
1002 }
1003 }
1004
1005 array_shift($this->ns_contexts);
1006
1007 #print str_repeat(" ", $this->depth * $this->indent) . "end_element('$name')" ."\n";
1008
1009 $this->depth--;
1010 }
1011
1012 function start_ns($parser, $prefix, $uri) {
1013 #print str_repeat(" ", $this->depth * $this->indent) . "starting: " . $prefix . ":" . $uri . "\n";
1014 array_push($this->ns_decls, array($prefix,$uri));
1015 }
1016
1017 function end_ns($parser, $prefix) {
1018 #print str_repeat(" ", $this->depth * $this->indent) . "ending: #" . $prefix . "#\n";
1019 }
1020
1021 function cdata($parser, $data) {
1022 #print str_repeat(" ", $this->depth * $this->indent) . "data: #" . $data . "#\n";
1023 if(!empty($this->in_content)) {
1024 // handle self-closing tags (case: text node found, need to close element started)
1025 if (strpos($this->in_content[count($this->in_content)-1], '<') !== false) {
1026 array_push($this->in_content, ">");
1027 }
1028 array_push($this->in_content, $this->xml_escape($data));
1029 }
1030 }
1031
1032 function _default($parser, $data) {
1033 # when does this gets called?
1034 }
1035
1036
1037 function ns_to_prefix($qname) {
1038 $components = split(":", $qname);
1039 $name = array_pop($components);
1040
1041 if(!empty($components)) {
1042 $ns = join(":",$components);
1043 foreach($this->ns_contexts as $context) {
1044 foreach($context as $mapping) {
1045 if($mapping[1] == $ns && strlen($mapping[0]) > 0) {
1046 return "$mapping[0]:$name";
1047 }
1048 }
1049 }
1050 }
1051 return $name;
1052 }
1053
1054 function xml_escape($string)
1055 {
1056 return str_replace(array('&','"',"'",'<','>'),
1057 array('&amp;','&quot;','&apos;','&lt;','&gt;'),
1058 $string );
1059 }
1060}
1061
1062?>
Note: See TracBrowser for help on using the repository browser.