]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/import/blogger.php
Wordpress 2.8.5
[autoinstalls/wordpress.git] / wp-admin / import / blogger.php
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  */
17 define( '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  */
27 define( '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  */
37 define( 'STATUS_INTERVAL',     3 );
38
39 /**
40  * Blogger Importer class
41  *
42  * @since unknown
43  */
44 class 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">
850 td { text-align: center; line-height: 2em;}
851 thead 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
885 register_importer('blogger', __('Blogger'), __('Import posts, comments, and users from a Blogger blog.'), array ($blogger_import, 'start'));
886
887 class AtomEntry {
888         var $links = array();
889         var $categories = array();
890 }
891
892 class 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 ?>