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