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