PHP Cross Reference of WordPress Subversion HEAD |
| [ Index ] [ Classes ] [ Functions ] [ Variables ] [ Constants ] |
[Summary view] [Print] [Text view]
1 <?php 2 3 define( 'MAX_RESULTS', 50 ); // How many records per GData query 4 define( 'MAX_EXECUTION_TIME', 20 ); // How many seconds to let the script run 5 define( 'STATUS_INTERVAL', 3 ); // How many seconds between status bar updates 6 7 class Blogger_Import { 8 9 // Shows the welcome screen and the magic auth link. 10 function greet() { 11 $next_url = get_option('siteurl') . '/wp-admin/index.php?import=blogger&noheader=true'; 12 $auth_url = "https://www.google.com/accounts/AuthSubRequest"; 13 $title = __('Import Blogger'); 14 $welcome = __('Howdy! This importer allows you to import posts and comments from your Blogger account into your WordPress blog.'); 15 $prereqs = __('To use this importer, you must have a Google account, an upgraded (New, was Beta) blog, and it must be on blogspot or a custom domain (not FTP).'); 16 $stepone = __('The first thing you need to do is tell Blogger to let WordPress access your account. You will be sent back here after providing authorization.'); 17 $auth = __('Authorize'); 18 19 echo " 20 <div class='wrap'><h2>$title</h2><p>$welcome</p><p>$prereqs</p><p>$stepone</p> 21 <form action='$auth_url' method='get'> 22 <p class='submit' style='text-align:left;'> 23 <input type='submit' value='$auth' /> 24 <input type='hidden' name='scope' value='http://www.blogger.com/feeds/' /> 25 <input type='hidden' name='session' value='1' /> 26 <input type='hidden' name='secure' value='0' /> 27 <input type='hidden' name='next' value='$next_url' /> 28 </p> 29 </form> 30 </div>\n"; 31 } 32 33 function uh_oh($title, $message, $info) { 34 echo "<div class='wrap'><h2>$title</h2><p>$message</p><pre>$info</pre></div>"; 35 } 36 37 function auth() { 38 // We have a single-use token that must be upgraded to a session token. 39 $token = preg_replace( '/[^-_0-9a-zA-Z]/', '', $_GET['token'] ); 40 $headers = array( 41 "GET /accounts/AuthSubSessionToken HTTP/1.0", 42 "Authorization: AuthSub token=\"$token\"" 43 ); 44 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 45 $sock = $this->_get_auth_sock( ); 46 if ( ! $sock ) return false; 47 $response = $this->_txrx( $sock, $request ); 48 preg_match( '/token=([-_0-9a-z]+)/i', $response, $matches ); 49 if ( empty( $matches[1] ) ) { 50 $this->uh_oh( 51 __( 'Authorization failed' ), 52 __( 'Something went wrong. If the problem persists, send this info to support:' ), 53 htmlspecialchars($response) 54 ); 55 return false; 56 } 57 $this->token = $matches[1]; 58 59 wp_redirect( remove_query_arg( array( 'token', 'noheader' ) ) ); 60 } 61 62 function get_token_info() { 63 $headers = array( 64 "GET /accounts/AuthSubTokenInfo HTTP/1.0", 65 "Authorization: AuthSub token=\"$this->token\"" 66 ); 67 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 68 $sock = $this->_get_auth_sock( ); 69 if ( ! $sock ) return; 70 $response = $this->_txrx( $sock, $request ); 71 return $this->parse_response($response); 72 } 73 74 function token_is_valid() { 75 $info = $this->get_token_info(); 76 77 if ( $info['code'] == 200 ) 78 return true; 79 80 return false; 81 } 82 83 function show_blogs($iter = 0) { 84 if ( empty($this->blogs) ) { 85 $headers = array( 86 "GET /feeds/default/blogs HTTP/1.0", 87 "Host: www.blogger.com", 88 "Authorization: AuthSub token=\"$this->token\"" 89 ); 90 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 91 $sock = $this->_get_blogger_sock( ); 92 if ( ! $sock ) return; 93 $response = $this->_txrx( $sock, $request ); 94 95 // Quick and dirty XML mining. 96 list( $headers, $xml ) = explode( "\r\n\r\n", $response ); 97 $p = xml_parser_create(); 98 xml_parse_into_struct($p, $xml, $vals, $index); 99 xml_parser_free($p); 100 101 $this->title = $vals[$index['TITLE'][0]]['value']; 102 103 // Give it a few retries... this step often flakes out the first time. 104 if ( empty( $index['ENTRY'] ) ) { 105 if ( $iter < 3 ) { 106 return $this->show_blogs($iter + 1); 107 } else { 108 $this->uh_oh( 109 __('Trouble signing in'), 110 __('We were not able to gain access to your account. Try starting over.'), 111 '' 112 ); 113 return false; 114 } 115 } 116 117 foreach ( $index['ENTRY'] as $i ) { 118 $blog = array(); 119 while ( ( $tag = $vals[$i] ) && ! ( $tag['tag'] == 'ENTRY' && $tag['type'] == 'close' ) ) { 120 if ( $tag['tag'] == 'TITLE' ) { 121 $blog['title'] = $tag['value']; 122 } elseif ( $tag['tag'] == 'SUMMARY' ) { 123 $blog['summary'] == $tag['value']; 124 } elseif ( $tag['tag'] == 'LINK' ) { 125 if ( $tag['attributes']['REL'] == 'alternate' && $tag['attributes']['TYPE'] == 'text/html' ) { 126 $parts = parse_url( $tag['attributes']['HREF'] ); 127 $blog['host'] = $parts['host']; 128 } elseif ( $tag['attributes']['REL'] == 'edit' ) 129 $blog['gateway'] = $tag['attributes']['HREF']; 130 } 131 ++$i; 132 } 133 if ( ! empty ( $blog ) ) { 134 $blog['total_posts'] = $this->get_total_results('posts', $blog['host']); 135 $blog['total_comments'] = $this->get_total_results('comments', $blog['host']); 136 $blog['mode'] = 'init'; 137 $this->blogs[] = $blog; 138 } 139 } 140 141 if ( empty( $this->blogs ) ) { 142 $this->uh_oh( 143 __('No blogs found'), 144 __('We were able to log in but there were no blogs. Try a different account next time.'), 145 '' 146 ); 147 return false; 148 } 149 } 150 //echo '<pre>'.print_r($this,1).'</pre>'; 151 $start = js_escape( __('Import') ); 152 $continue = js_escape( __('Continue') ); 153 $stop = js_escape( __('Importing...') ); 154 $authors = js_escape( __('Set Authors') ); 155 $loadauth = js_escape( __('Preparing author mapping form...') ); 156 $authhead = js_escape( __('Final Step: Author Mapping') ); 157 $nothing = js_escape( __('Nothing was imported. Had you already imported this blog?') ); 158 $title = __('Blogger Blogs'); 159 $name = __('Blog Name'); 160 $url = __('Blog URL'); 161 $action = __('The Magic Button'); 162 $posts = __('Posts'); 163 $comments = __('Comments'); 164 $noscript = __('This feature requires Javascript but it seems to be disabled. Please enable Javascript and then reload this page. Don\'t worry, you can turn it back off when you\'re done.'); 165 166 $interval = STATUS_INTERVAL * 1000; 167 168 foreach ( $this->blogs as $i => $blog ) { 169 if ( $blog['mode'] == 'init' ) 170 $value = $start; 171 elseif ( $blog['mode'] == 'posts' || $blog['mode'] == 'comments' ) 172 $value = $continue; 173 else 174 $value = $authors; 175 $blogtitle = js_escape( $blog['title'] ); 176 $pdone = isset($blog['posts_done']) ? (int) $blog['posts_done'] : 0; 177 $cdone = isset($blog['comments_done']) ? (int) $blog['comments_done'] : 0; 178 $init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}'," . $this->get_js_status($i) . ');'; 179 $pstat = "<div class='ind' id='pind$i'> </div><div id='pstat$i' class='stat'>$pdone/{$blog['total_posts']}</div>"; 180 $cstat = "<div class='ind' id='cind$i'> </div><div id='cstat$i' class='stat'>$cdone/{$blog['total_comments']}</div>"; 181 $rows .= "<tr id='blog$i'><td class='blogtitle'>$blogtitle</td><td class='bloghost'>{$blog['host']}</td><td class='bar'>$pstat</td><td class='bar'>$cstat</td><td class='submit'><input type='submit' id='submit$i' value='$value' /><input type='hidden' name='blog' value='$i' /></td></tr>\n"; 182 } 183 184 echo "<div class='wrap'><h2>$title</h2><noscript>$noscript</noscript><table cellpadding='5px'><thead><td>$name</td><td>$url</td><td>$posts</td><td>$comments</td><td>$action</td></thead>\n$rows</table></form></div>"; 185 echo " 186 <script type='text/javascript'> 187 var strings = {cont:'$continue',stop:'$stop',stopping:'$stopping',authors:'$authors',nothing:'$nothing'}; 188 var blogs = {}; 189 function blog(i, title, mode, status){ 190 this.blog = i; 191 this.mode = mode; 192 this.title = title; 193 this.status = status; 194 this.button = document.getElementById('submit'+this.blog); 195 }; 196 blog.prototype = { 197 start: function() { 198 this.cont = true; 199 this.kick(); 200 this.check(); 201 }, 202 kick: function() { 203 ++this.kicks; 204 var i = this.blog; 205 jQuery.post('admin.php?import=blogger&noheader=true',{blog:this.blog},function(text,result){blogs[i].kickd(text,result)}); 206 }, 207 check: function() { 208 ++this.checks; 209 var i = this.blog; 210 jQuery.post('admin.php?import=blogger&noheader=true&status=true',{blog:this.blog},function(text,result){blogs[i].checkd(text,result)}); 211 }, 212 kickd: function(text, result) { 213 if ( result == 'error' ) { 214 // TODO: exception handling 215 if ( this.cont ) 216 setTimeout('blogs['+this.blog+'].kick()', 1000); 217 } else { 218 if ( text == 'done' ) { 219 this.stop(); 220 this.done(); 221 } else if ( text == 'nothing' ) { 222 this.stop(); 223 this.nothing(); 224 } else if ( text == 'continue' ) { 225 this.kick(); 226 } else if ( this.mode = 'stopped' ) 227 jQuery(this.button).attr('value', strings.cont); 228 } 229 --this.kicks; 230 }, 231 checkd: function(text, result) { 232 if ( result == 'error' ) { 233 // TODO: exception handling 234 } else { 235 eval('this.status='+text); 236 jQuery('#pstat'+this.blog).empty().append(this.status.p1+'/'+this.status.p2); 237 jQuery('#cstat'+this.blog).empty().append(this.status.c1+'/'+this.status.c2); 238 this.update(); 239 if ( this.cont || this.kicks > 0 ) 240 setTimeout('blogs['+this.blog+'].check()', $interval); 241 } 242 --this.checks; 243 }, 244 update: function() { 245 jQuery('#pind'+this.blog).width(((this.status.p1>0&&this.status.p2>0)?(this.status.p1/this.status.p2*jQuery('#pind'+this.blog).parent().width()):1)+'px'); 246 jQuery('#cind'+this.blog).width(((this.status.c1>0&&this.status.c2>0)?(this.status.c1/this.status.c2*jQuery('#cind'+this.blog).parent().width()):1)+'px'); 247 }, 248 stop: function() { 249 this.cont = false; 250 }, 251 done: function() { 252 this.mode = 'authors'; 253 jQuery(this.button).attr('value', strings.authors); 254 }, 255 nothing: function() { 256 this.mode = 'nothing'; 257 jQuery(this.button).remove(); 258 alert(strings.nothing); 259 }, 260 getauthors: function() { 261 if ( jQuery('div.wrap').length > 1 ) 262 jQuery('div.wrap').gt(0).remove(); 263 jQuery('div.wrap').empty().append('<h2>$authhead</h2><h3>' + this.title + '</h3>'); 264 jQuery('div.wrap').append('<p id=\"auth\">$loadauth</p>'); 265 jQuery('p#auth').load('index.php?import=blogger&noheader=true&authors=1',{blog:this.blog}); 266 }, 267 init: function() { 268 this.update(); 269 var i = this.blog; 270 jQuery(this.button).bind('click', function(){return blogs[i].click();}); 271 this.kicks = 0; 272 this.checks = 0; 273 }, 274 click: function() { 275 if ( this.mode == 'init' || this.mode == 'stopped' || this.mode == 'posts' || this.mode == 'comments' ) { 276 this.mode = 'started'; 277 this.start(); 278 jQuery(this.button).attr('value', strings.stop); 279 } else if ( this.mode == 'started' ) { 280 return false; // let it run... 281 this.mode = 'stopped'; 282 this.stop(); 283 if ( this.checks > 0 || this.kicks > 0 ) { 284 this.mode = 'stopping'; 285 jQuery(this.button).attr('value', strings.stopping); 286 } else { 287 jQuery(this.button).attr('value', strings.cont); 288 } 289 } else if ( this.mode == 'authors' ) { 290 document.location = 'index.php?import=blogger&authors=1&blog='+this.blog; 291 //this.mode = 'authors2'; 292 //this.getauthors(); 293 } 294 return false; 295 } 296 }; 297 $init 298 jQuery.each(blogs, function(i, me){me.init();}); 299 </script>\n"; 300 } 301 302 // Handy function for stopping the script after a number of seconds. 303 function have_time() { 304 global $importer_started; 305 if ( time() - $importer_started > MAX_EXECUTION_TIME ) 306 die('continue'); 307 return true; 308 } 309 310 function get_total_results($type, $host) { 311 $headers = array( 312 "GET /feeds/$type/default?max-results=1&start-index=2 HTTP/1.0", 313 "Host: $host", 314 "Authorization: AuthSub token=\"$this->token\"" 315 ); 316 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 317 $sock = $this->_get_blogger_sock( $host ); 318 if ( ! $sock ) return; 319 $response = $this->_txrx( $sock, $request ); 320 $response = $this->parse_response( $response ); 321 $parser = xml_parser_create(); 322 xml_parse_into_struct($parser, $response['body'], $struct, $index); 323 xml_parser_free($parser); 324 $total_results = $struct[$index['OPENSEARCH:TOTALRESULTS'][0]]['value']; 325 return (int) $total_results; 326 } 327 328 function import_blog($blogID) { 329 global $importing_blog; 330 $importing_blog = $blogID; 331 332 if ( isset($_GET['authors']) ) 333 return print($this->get_author_form()); 334 335 header('Content-Type: text/plain'); 336 337 if ( isset($_GET['status']) ) 338 die($this->get_js_status()); 339 340 if ( isset($_GET['saveauthors']) ) 341 die($this->save_authors()); 342 343 $blog = $this->blogs[$blogID]; 344 $total_results = $this->get_total_results('posts', $blog['host']); 345 $this->blogs[$importing_blog]['total_posts'] = $total_results; 346 347 $start_index = $total_results - MAX_RESULTS + 1; 348 349 if ( isset( $this->blogs[$importing_blog]['posts_start_index'] ) ) 350 $start_index = (int) $this->blogs[$importing_blog]['posts_start_index']; 351 elseif ( $total_results > MAX_RESULTS ) 352 $start_index = $total_results - MAX_RESULTS + 1; 353 else 354 $start_index = 1; 355 356 // This will be positive until we have finished importing posts 357 if ( $start_index > 0 ) { 358 // Grab all the posts 359 $this->blogs[$importing_blog]['mode'] = 'posts'; 360 $query = "start-index=$start_index&max-results=" . MAX_RESULTS; 361 do { 362 $index = $struct = $entries = array(); 363 $headers = array( 364 "GET /feeds/posts/default?$query HTTP/1.0", 365 "Host: {$blog['host']}", 366 "Authorization: AuthSub token=\"$this->token\"" 367 ); 368 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 369 $sock = $this->_get_blogger_sock( $blog['host'] ); 370 if ( ! $sock ) return; // TODO: Error handling 371 $response = $this->_txrx( $sock, $request ); 372 373 $response = $this->parse_response( $response ); 374 375 // Extract the entries and send for insertion 376 preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches ); 377 if ( count( $matches[0] ) ) { 378 $entries = array_reverse($matches[0]); 379 foreach ( $entries as $entry ) { 380 $entry = "<feed>$entry</feed>"; 381 $AtomParser = new AtomParser(); 382 $AtomParser->parse( $entry ); 383 $result = $this->import_post($AtomParser->entry); 384 if ( is_wp_error( $result ) ) 385 return $result; 386 unset($AtomParser); 387 } 388 } else break; 389 390 // Get the 'previous' query string which we'll use on the next iteration 391 $query = ''; 392 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches); 393 if ( count( $matches[1] ) ) 394 foreach ( $matches[1] as $match ) 395 if ( preg_match('/rel=.previous./', $match) ) 396 $query = html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match) ); 397 398 if ( $query ) { 399 parse_str($query, $q); 400 $this->blogs[$importing_blog]['posts_start_index'] = (int) $q['start-index']; 401 } else 402 $this->blogs[$importing_blog]['posts_start_index'] = 0; 403 $this->save_vars(); 404 } while ( !empty( $query ) && $this->have_time() ); 405 } 406 407 $total_results = $this->get_total_results( 'comments', $blog['host'] ); 408 $this->blogs[$importing_blog]['total_comments'] = $total_results; 409 410 if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) ) 411 $start_index = (int) $this->blogs[$importing_blog]['comments_start_index']; 412 elseif ( $total_results > MAX_RESULTS ) 413 $start_index = $total_results - MAX_RESULTS + 1; 414 else 415 $start_index = 1; 416 417 if ( $start_index > 0 ) { 418 // Grab all the comments 419 $this->blogs[$importing_blog]['mode'] = 'comments'; 420 $query = "start-index=$start_index&max-results=" . MAX_RESULTS; 421 do { 422 $index = $struct = $entries = array(); 423 $headers = array( 424 "GET /feeds/comments/default?$query HTTP/1.0", 425 "Host: {$blog['host']}", 426 "Authorization: AuthSub token=\"$this->token\"" 427 ); 428 $request = join( "\r\n", $headers ) . "\r\n\r\n"; 429 $sock = $this->_get_blogger_sock( $blog['host'] ); 430 if ( ! $sock ) return; // TODO: Error handling 431 $response = $this->_txrx( $sock, $request ); 432 433 $response = $this->parse_response( $response ); 434 435 // Extract the comments and send for insertion 436 preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches ); 437 if ( count( $matches[0] ) ) { 438 $entries = array_reverse( $matches[0] ); 439 foreach ( $entries as $entry ) { 440 $entry = "<feed>$entry</feed>"; 441 $AtomParser = new AtomParser(); 442 $AtomParser->parse( $entry ); 443 $this->import_comment($AtomParser->entry); 444 unset($AtomParser); 445 } 446 } 447 448 // Get the 'previous' query string which we'll use on the next iteration 449 $query = ''; 450 $links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches); 451 if ( count( $matches[1] ) ) 452 foreach ( $matches[1] as $match ) 453 if (