]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/import/blogger.php
Wordpress 2.6.2
[autoinstalls/wordpress.git] / wp-admin / import / blogger.php
1 <?php
2
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
6
7 class 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 and an upgraded (New, was Beta) blog hosted on blogspot.com 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' class='button' value='$auth' />
24                                         <input type='hidden' name='scope' value='http://www.blogger.com/feeds/' />
25                                         <input type='hidden' name='session' value='1' />
26                                         <input type='hidden' name='secure' value='0' />
27                                         <input type='hidden' name='next' value='$next_url' />
28                                 </p>
29                         </form>
30                 </div>\n";
31         }
32
33         function uh_oh($title, $message, $info) {
34                 echo "<div class='wrap'><h2>$title</h2><p>$message</p><pre>$info</pre></div>";
35         }
36
37         function auth() {
38                 // We have a single-use token that must be upgraded to a session token.
39                 $token = preg_replace( '/[^-_0-9a-zA-Z]/', '', $_GET['token'] );
40                 $headers = array(
41                         "GET /accounts/AuthSubSessionToken HTTP/1.0",
42                         "Authorization: AuthSub token=\"$token\""
43                 );
44                 $request = join( "\r\n", $headers ) . "\r\n\r\n";
45                 $sock = $this->_get_auth_sock( );
46                 if ( ! $sock ) return false;
47                 $response = $this->_txrx( $sock, $request );
48                 preg_match( '/token=([-_0-9a-z]+)/i', $response, $matches );
49                 if ( empty( $matches[1] ) ) {
50                         $this->uh_oh(
51                                 __( 'Authorization failed' ),
52                                 __( 'Something went wrong. If the problem persists, send this info to support:' ),
53                                 htmlspecialchars($response)
54                         );
55                         return false;
56                 }
57                 $this->token = $matches[1];
58
59                 wp_redirect( remove_query_arg( array( 'token', 'noheader' ) ) );
60         }
61
62         function get_token_info() {
63                 $headers = array(
64                         "GET /accounts/AuthSubTokenInfo  HTTP/1.0",
65                         "Authorization: AuthSub token=\"$this->token\""
66                 );
67                 $request = join( "\r\n", $headers ) . "\r\n\r\n";
68                 $sock = $this->_get_auth_sock( );
69                 if ( ! $sock ) return;
70                 $response = $this->_txrx( $sock, $request );
71                 return $this->parse_response($response);
72         }
73
74         function token_is_valid() {
75                 $info = $this->get_token_info();
76
77                 if ( $info['code'] == 200 )
78                         return true;
79
80                 return false;
81         }
82
83         function show_blogs($iter = 0) {
84                 if ( empty($this->blogs) ) {
85                         $headers = array(
86                                 "GET /feeds/default/blogs HTTP/1.0",
87                                 "Host: www.blogger.com",
88                                 "Authorization: AuthSub token=\"$this->token\""
89                         );
90                         $request = join( "\r\n", $headers ) . "\r\n\r\n";
91                         $sock = $this->_get_blogger_sock( );
92                         if ( ! $sock ) return;
93                         $response = $this->_txrx( $sock, $request );
94
95                         // Quick and dirty XML mining.
96                         list( $headers, $xml ) = explode( "\r\n\r\n", $response );
97                         $p = xml_parser_create();
98                         xml_parse_into_struct($p, $xml, $vals, $index);
99                         xml_parser_free($p);
100
101                         $this->title = $vals[$index['TITLE'][0]]['value'];
102
103                         // Give it a few retries... this step often flakes out the first time.
104                         if ( empty( $index['ENTRY'] ) ) {
105                                 if ( $iter < 3 ) {
106                                         return $this->show_blogs($iter + 1);
107                                 } else {
108                                         $this->uh_oh(
109                                                 __('Trouble signing in'),
110                                                 __('We were not able to gain access to your account. Try starting over.'),
111                                                 ''
112                                         );
113                                         return false;
114                                 }
115                         }
116
117                         foreach ( $index['ENTRY'] as $i ) {
118                                 $blog = array();
119                                 while ( ( $tag = $vals[$i] ) && ! ( $tag['tag'] == 'ENTRY' && $tag['type'] == 'close' ) ) {
120                                         if ( $tag['tag'] == 'TITLE' ) {
121                                                 $blog['title'] = $tag['value'];
122                                         } elseif ( $tag['tag'] == 'SUMMARY' ) {
123                                                 $blog['summary'] == $tag['value'];
124                                         } elseif ( $tag['tag'] == 'LINK' ) {
125                                                 if ( $tag['attributes']['REL'] == 'alternate' && $tag['attributes']['TYPE'] == 'text/html' ) {
126                                                         $parts = parse_url( $tag['attributes']['HREF'] );
127                                                         $blog['host'] = $parts['host'];
128                                                 } elseif ( $tag['attributes']['REL'] == 'edit' )
129                                                         $blog['gateway'] = $tag['attributes']['HREF'];
130                                         }
131                                         ++$i;
132                                 }
133                                 if ( ! empty ( $blog ) ) {
134                                         $blog['total_posts'] = $this->get_total_results('posts', $blog['host']);
135                                         $blog['total_comments'] = $this->get_total_results('comments', $blog['host']);
136                                         $blog['mode'] = 'init';
137                                         $this->blogs[] = $blog;
138                                 }
139                         }
140
141                         if ( empty( $this->blogs ) ) {
142                                 $this->uh_oh(
143                                         __('No blogs found'),
144                                         __('We were able to log in but there were no blogs. Try a different account next time.'),
145                                         ''
146                                 );
147                                 return false;
148                         }
149                 }
150 //echo '<pre>'.print_r($this,1).'</pre>';
151                 $start    = js_escape( __('Import') );
152                 $continue = js_escape( __('Continue') );
153                 $stop     = js_escape( __('Importing...') );
154                 $authors  = js_escape( __('Set Authors') );
155                 $loadauth = js_escape( __('Preparing author mapping form...') );
156                 $authhead = js_escape( __('Final Step: Author Mapping') );
157                 $nothing  = js_escape( __('Nothing was imported. Had you already imported this blog?') );
158                 $title    = __('Blogger Blogs');
159                 $name     = __('Blog Name');
160                 $url      = __('Blog URL');
161                 $action   = __('The Magic Button');
162                 $posts    = __('Posts');
163                 $comments = __('Comments');
164                 $noscript = __('This feature requires Javascript but it seems to be disabled. Please enable Javascript and then reload this page. Don\'t worry, you can turn it back off when you\'re done.');
165
166                 $interval = STATUS_INTERVAL * 1000;
167
168                 foreach ( $this->blogs as $i => $blog ) {
169                         if ( $blog['mode'] == 'init' )
170                                 $value = $start;
171                         elseif ( $blog['mode'] == 'posts' || $blog['mode'] == 'comments' )
172                                 $value = $continue;
173                         else
174                                 $value = $authors;
175                         $blogtitle = js_escape( $blog['title'] );
176                         $pdone = isset($blog['posts_done']) ? (int) $blog['posts_done'] : 0;
177                         $cdone = isset($blog['comments_done']) ? (int) $blog['comments_done'] : 0;
178                         $init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}'," . $this->get_js_status($i) . ');';
179                         $pstat = "<div class='ind' id='pind$i'>&nbsp;</div><div id='pstat$i' class='stat'>$pdone/{$blog['total_posts']}</div>";
180                         $cstat = "<div class='ind' id='cind$i'>&nbsp;</div><div id='cstat$i' class='stat'>$cdone/{$blog['total_comments']}</div>";
181                         $rows .= "<tr id='blog$i'><td class='blogtitle'>$blogtitle</td><td class='bloghost'>{$blog['host']}</td><td class='bar'>$pstat</td><td class='bar'>$cstat</td><td class='submit'><input type='submit' class='button' 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                                                 $result = $this->import_post($AtomParser->entry);
384                                                 if ( is_wp_error( $result ) )
385                                                         return $result;
386                                                 unset($AtomParser);
387                                         }
388                                 } else break;
389
390                                 // Get the 'previous' query string which we'll use on the next iteration
391                                 $query = '';
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) );
397
398                                 if ( $query ) {
399                                         parse_str($query, $q);
400                                         $this->blogs[$importing_blog]['posts_start_index'] = (int) $q['start-index'];
401                                 } else
402                                         $this->blogs[$importing_blog]['posts_start_index'] = 0;
403                                 $this->save_vars();
404                         } while ( !empty( $query ) && $this->have_time() );
405                 }
406
407                 $total_results = $this->get_total_results( 'comments', $blog['host'] );
408                 $this->blogs[$importing_blog]['total_comments'] = $total_results;
409
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;
414                 else
415                         $start_index = 1;
416
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;
421                         do {
422                                 $index = $struct = $entries = array();
423                                 $headers = array(
424                                         "GET /feeds/comments/default?$query HTTP/1.0",
425                                         "Host: {$blog['host']}",
426                                         "Authorization: AuthSub token=\"$this->token\""
427                                 );
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 );
432
433                                 $response = $this->parse_response( $response );
434
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);
444                                                 unset($AtomParser);
445                                         }
446                                 }
447
448                                 // Get the 'previous' query string which we'll use on the next iteration
449                                 $query = '';
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) );
455
456                                 parse_str($query, $q);
457
458                                 $this->blogs[$importing_blog]['comments_start_index'] = (int) $q['start-index'];
459                                 $this->save_vars();
460                         } while ( !empty( $query ) && $this->have_time() );
461                 }
462                 $this->blogs[$importing_blog]['mode'] = 'authors';
463                 $this->save_vars();
464                 if ( !$this->blogs[$importing_blog]['posts_done'] && !$this->blogs[$importing_blog]['comments_done'] )
465                         die('nothing');
466                 do_action('import_done', 'blogger');
467                 die('done');
468         }
469
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);
477         }
478
479         function no_apos( $string ) {
480                 return str_replace( '&apos;', "'", $string);
481         }
482
483         function min_whitespace( $string ) {
484                 return preg_replace( '|\s+|', ' ', $string );
485         }
486
487         function import_post( $entry ) {
488                 global $wpdb, $importing_blog;
489
490                 // The old permalink is all Blogger gives us to link comments to their posts.
491                 if ( isset( $entry->draft ) )
492                         $rel = 'self';
493                 else
494                         $rel = 'alternate';
495                 foreach ( $entry->links as $link ) {
496                         if ( $link['rel'] == $rel ) {
497                                 $parts = parse_url( $link['href'] );
498                                 $entry->old_permalink = $parts['path'];
499                                 break;
500                         }
501                 }
502
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';
507
508                 // Clean up content
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);
512
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'];
519                 } else {
520                         $post = compact('post_date', 'post_content', 'post_title', 'post_status');
521
522                         $post_id = wp_insert_post($post);
523                         if ( is_wp_error( $post_id ) )
524                                 return $post_id;
525
526                         wp_create_categories( array_map( 'addslashes', $entry->categories ), $post_id );
527
528                         $author = $this->no_apos( strip_tags( $entry->author ) );
529
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 );
533
534                         $this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
535                         ++$this->blogs[$importing_blog]['posts_done'];
536                 }
537                 $this->save_vars();
538                 return;
539         }
540
541         function import_comment( $entry ) {
542                 global $importing_blog;
543
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'];
550                                 break;
551                         }
552                 }
553
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 ) ) );
560
561                 // Clean up 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);
565
566                 // Checks for duplicates
567                 if (
568                         isset( $this->blogs[$importing_blog]['comments'][$entry->old_permalink] ) ||
569                         comment_exists( $comment_author, $comment_date )
570                 ) {
571                         ++$this->blogs[$importing_blog]['comments_skipped'];
572                 } else {
573                         $comment = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_date', 'comment_content');
574
575                         $comment_id = wp_insert_comment($comment);
576
577                         $this->blogs[$importing_blog]['comments'][$entry->old_permalink] = $comment_id;
578
579                         ++$this->blogs[$importing_blog]['comments_done'];
580                 }
581                 $this->save_vars();
582         }
583
584         function get_js_status($blog = false) {
585                 global $importing_blog;
586                 if ( $blog === false )
587                         $blog = $this->blogs[$importing_blog];
588                 else
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}";
595         }
596
597         function get_author_form($blog = false) {
598                 global $importing_blog, $wpdb, $current_user;
599                 if ( $blog === false )
600                         $blog = & $this->blogs[$importing_blog];
601                 else
602                         $blog = & $this->blogs[$blog];
603
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));
608                         $this->save_vars();
609                 }
610
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') );
617
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>";
620
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='button authorsubmit' value='$submit' /></td></tr></table></form></div>";
622         }
623
624         function get_user_options($current) {
625                 global $wpdb, $importer_users;
626                 if ( ! isset( $importer_users ) )
627                         $importer_users = (array) get_users_of_blog();
628
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>";
632                 }
633
634                 return $options;
635         }
636
637         function save_authors() {
638                 global $importing_blog, $wpdb;
639                 $authors = (array) $_POST['authors'];
640
641                 $host = $this->blogs[$importing_blog]['host'];
642
643                 // Get an array of posts => authors
644                 $post_ids = (array) $wpdb->get_col( $wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'blogger_blog' AND meta_value = %s", $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;
649
650                 foreach ( $authors as $author => $user_id ) {
651                         $user_id = (int) $user_id;
652
653                         // Skip authors that haven't been changed
654                         if ( $user_id == $this->blogs[$importing_blog]['authors'][$author][1] )
655                                 continue;
656
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);
660
661                         $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET post_author = %d WHERE id IN ($post_ids)", $user_id) );
662                         $this->blogs[$importing_blog]['authors'][$author][1] = $user_id;
663                 }
664                 $this->save_vars();
665
666                 wp_redirect('edit.php');
667         }
668
669         function _get_auth_sock() {
670                 // Connect to https://www.google.com
671                 if ( !$sock = @ fsockopen('ssl://www.google.com', 443, $errno, $errstr) ) {
672                         $this->uh_oh(
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:'),
675                                 "$errstr ($errno)"
676                         );
677                         return false;
678                 }
679                 return $sock;
680         }
681
682         function _get_blogger_sock($host = 'www2.blogger.com') {
683                 if ( !$sock = @ fsockopen($host, 80, $errno, $errstr) ) {
684                         $this->uh_oh(
685                                 sprintf( __('Could not connect to %s'), $host ),
686                                 __('There was a problem opening a connection to Blogger. This is what went wrong:'),
687                                 "$errstr ($errno)"
688                         );
689                         return false;
690                 }
691                 return $sock;
692         }
693
694         function _txrx( $sock, $request ) {
695                 fwrite( $sock, $request );
696                 while ( ! feof( $sock ) )
697                         $response .= @ fread ( $sock, 8192 );
698                 fclose( $sock );
699                 return $response;
700         }
701
702         function revoke($token) {
703                 $headers = array(
704                         "GET /accounts/AuthSubRevokeToken HTTP/1.0",
705                         "Authorization: AuthSub token=\"$token\""
706                 );
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 );
711         }
712
713         function restart() {
714                 global $wpdb;
715                 $options = get_option( 'blogger_importer' );
716
717                 if ( isset( $options['token'] ) )
718                         $this->revoke( $options['token'] );
719
720                 delete_option('blogger_importer');
721                 $wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'blogger_author'");
722                 wp_redirect('?import=blogger');
723         }
724
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);
730
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]; }
734
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";
740                 }
741
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); }
745
746                 return array("code" => $response_code, "header" => $response_header_array, "cookies" => $cookie_array, "body" => $response_body);
747         }
748
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 &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>';
757                 echo '<li>'.__('For security, click the link below to reset this importer.').'</li>';
758                 echo '</ul>';
759         }
760
761         // Figures out what to do, then does it.
762         function start() {
763                 if ( isset($_POST['restart']) )
764                         $this->restart();
765
766                 $options = get_option('blogger_importer');
767
768                 if ( is_array($options) )
769                         foreach ( $options as $key => $value )
770                                 $this->$key = $value;
771
772                 if ( isset( $_REQUEST['blog'] ) ) {
773                         $blog = is_array($_REQUEST['blog']) ? array_shift( $keys = array_keys( $_REQUEST['blog'] ) ) : $_REQUEST['blog'];
774                         $blog = (int) $blog;
775                         $result = $this->import_blog( $blog );
776                         if ( is_wp_error( $result ) )
777                                 echo $result->get_error_message();
778                 } elseif ( isset($_GET['token']) )
779                         $this->auth();
780                 elseif ( $this->token && $this->token_is_valid() )
781                         $this->show_blogs();
782                 else
783                         $this->greet();
784
785                 $saved = $this->save_vars();
786
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' class='button' value='$submit' name='restart' /></p></form></div>";
792                 }
793         }
794
795         function save_vars() {
796                 $vars = get_object_vars($this);
797                 update_option( 'blogger_importer', $vars );
798
799                 return !empty($vars);
800         }
801
802         function admin_head() {
803 ?>
804 <style type="text/css">
805 td { text-align: center; line-height: 2em;}
806 thead td { font-weight: bold; }
807 .bar {
808         width: 200px;
809         text-align: left;
810         line-height: 2em;
811         padding: 0px;
812 }
813 .ind {
814         position: absolute;
815         background-color: #83B4D8;
816         width: 1px;
817         z-index: 9;
818 }
819 .stat {
820         z-index: 10;
821         position: relative;
822         text-align: center;
823 }
824 </style>
825 <?php
826         }
827
828         function Blogger_Import() {
829                 global $importer_started;
830                 $importer_started = time();
831                 if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) {
832                         wp_enqueue_script('jquery');
833                         add_action('admin_head', array(&$this, 'admin_head'));
834                 }
835         }
836 }
837
838 $blogger_import = new Blogger_Import();
839
840 register_importer('blogger', __('Blogger'), __('Import posts, comments, and users from a Blogger blog.'), array ($blogger_import, 'start'));
841
842 class AtomEntry {
843         var $links = array();
844         var $categories = array();
845 }
846
847 class AtomParser {
848
849         var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights');
850         var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft','author');
851
852         var $depth = 0;
853         var $indent = 2;
854         var $in_content;
855         var $ns_contexts = array();
856         var $ns_decls = array();
857         var $is_xhtml = false;
858         var $skipped_div = false;
859
860         var $entry;
861
862         function AtomParser() {
863
864                 $this->entry = new AtomEntry();
865                 $this->map_attrs_func = create_function('$k,$v', 'return "$k=\"$v\"";');
866                 $this->map_xmlns_func = create_function('$p,$n', '$xd = "xmlns"; if(strlen($n[0])>0) $xd .= ":{$n[0]}"; return "{$xd}=\"{$n[1]}\"";');
867         }
868
869         function parse($xml) {
870
871                 global $app_logging;
872                 array_unshift($this->ns_contexts, array());
873
874                 $parser = xml_parser_create_ns();
875                 xml_set_object($parser, $this);
876                 xml_set_element_handler($parser, "start_element", "end_element");
877                 xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);
878                 xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0);
879                 xml_set_character_data_handler($parser, "cdata");
880                 xml_set_default_handler($parser, "_default");
881                 xml_set_start_namespace_decl_handler($parser, "start_ns");
882                 xml_set_end_namespace_decl_handler($parser, "end_ns");
883
884                 $contents = "";
885
886                 xml_parse($parser, $xml);
887
888                 xml_parser_free($parser);
889
890                 return true;
891         }
892
893         function start_element($parser, $name, $attrs) {
894
895                 $tag = array_pop(split(":", $name));
896
897                 array_unshift($this->ns_contexts, $this->ns_decls);
898
899                 $this->depth++;
900
901                 if(!empty($this->in_content)) {
902                         $attrs_prefix = array();
903
904                         // resolve prefixes for attributes
905                         foreach($attrs as $key => $value) {
906                                 $attrs_prefix[$this->ns_to_prefix($key)] = $this->xml_escape($value);
907                         }
908                         $attrs_str = join(' ', array_map($this->map_attrs_func, array_keys($attrs_prefix), array_values($attrs_prefix)));
909                         if(strlen($attrs_str) > 0) {
910                                 $attrs_str = " " . $attrs_str;
911                         }
912
913                         $xmlns_str = join(' ', array_map($this->map_xmlns_func, array_keys($this->ns_contexts[0]), array_values($this->ns_contexts[0])));
914                         if(strlen($xmlns_str) > 0) {
915                                 $xmlns_str = " " . $xmlns_str;
916                         }
917
918                         // handle self-closing tags (case: a new child found right-away, no text node)
919                         if(count($this->in_content) == 2) {
920                                 array_push($this->in_content, ">");
921                         }
922
923                         array_push($this->in_content, "<". $this->ns_to_prefix($name) ."{$xmlns_str}{$attrs_str}");
924                 } else if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) {
925                         $this->in_content = array();
926                         $this->is_xhtml = $attrs['type'] == 'xhtml';
927                         array_push($this->in_content, array($tag,$this->depth));
928                 } else if($tag == 'link') {
929                         array_push($this->entry->links, $attrs);
930                 } else if($tag == 'category') {
931                         array_push($this->entry->categories, $attrs['term']);
932                 }
933
934                 $this->ns_decls = array();
935         }
936
937         function end_element($parser, $name) {
938
939                 $tag = array_pop(split(":", $name));
940
941                 if(!empty($this->in_content)) {
942                         if($this->in_content[0][0] == $tag &&
943                         $this->in_content[0][1] == $this->depth) {
944                                 array_shift($this->in_content);
945                                 if($this->is_xhtml) {
946                                         $this->in_content = array_slice($this->in_content, 2, count($this->in_content)-3);
947                                 }
948                                 $this->entry->$tag = join('',$this->in_content);
949                                 $this->in_content = array();
950                         } else {
951                                 $endtag = $this->ns_to_prefix($name);
952                                 if (strpos($this->in_content[count($this->in_content)-1], '<' . $endtag) !== false) {
953                                         array_push($this->in_content, "/>");
954                                 } else {
955                                         array_push($this->in_content, "</$endtag>");
956                                 }
957                         }
958                 }
959
960                 array_shift($this->ns_contexts);
961
962                 #print str_repeat(" ", $this->depth * $this->indent) . "end_element('$name')" ."\n";
963
964                 $this->depth--;
965         }
966
967         function start_ns($parser, $prefix, $uri) {
968                 #print str_repeat(" ", $this->depth * $this->indent) . "starting: " . $prefix . ":" . $uri . "\n";
969                 array_push($this->ns_decls, array($prefix,$uri));
970         }
971
972         function end_ns($parser, $prefix) {
973                 #print str_repeat(" ", $this->depth * $this->indent) . "ending: #" . $prefix . "#\n";
974         }
975
976         function cdata($parser, $data) {
977                 #print str_repeat(" ", $this->depth * $this->indent) . "data: #" . $data . "#\n";
978                 if(!empty($this->in_content)) {
979                         // handle self-closing tags (case: text node found, need to close element started)
980                         if (strpos($this->in_content[count($this->in_content)-1], '<') !== false) {
981                                 array_push($this->in_content, ">");
982                         }
983                         array_push($this->in_content, $this->xml_escape($data));
984                 }
985         }
986
987         function _default($parser, $data) {
988                 # when does this gets called?
989         }
990
991
992         function ns_to_prefix($qname) {
993                 $components = split(":", $qname);
994                 $name = array_pop($components);
995
996                 if(!empty($components)) {
997                         $ns = join(":",$components);
998                         foreach($this->ns_contexts as $context) {
999                                 foreach($context as $mapping) {
1000                                         if($mapping[1] == $ns && strlen($mapping[0]) > 0) {
1001                                                 return "$mapping[0]:$name";
1002                                         }
1003                                 }
1004                         }
1005                 }
1006                 return $name;
1007         }
1008
1009         function xml_escape($string)
1010         {
1011                          return str_replace(array('&','"',"'",'<','>'),
1012                                 array('&amp;','&quot;','&apos;','&lt;','&gt;'),
1013                                 $string );
1014         }
1015 }
1016
1017 ?>