| [ Index ] |
PHP Cross Reference of Wordpress 2.9.1 |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * LiveJournal API Importer 5 * 6 * @package WordPress 7 * @subpackage Importer 8 */ 9 10 // XML-RPC library for communicating with LiveJournal API 11 require_once ( ABSPATH . WPINC . '/class-IXR.php' ); 12 13 /** 14 * LiveJournal API Importer class 15 * 16 * Imports your LiveJournal contents into WordPress using the LJ API 17 * 18 * @since 2.8 19 */ 20 class LJ_API_Import { 21 22 var $comments_url = 'http://www.livejournal.com/export_comments.bml'; 23 var $ixr_url = 'http://www.livejournal.com/interface/xmlrpc'; 24 var $ixr; 25 var $username; 26 var $password; 27 var $comment_meta; 28 var $comments; 29 var $usermap; 30 var $postmap; 31 var $commentmap; 32 var $pointers = array(); 33 34 // This list taken from LJ, they don't appear to have an API for it 35 var $moods = array( '1' => 'aggravated', 36 '10' => 'discontent', 37 '100' => 'rushed', 38 '101' => 'contemplative', 39 '102' => 'nerdy', 40 '103' => 'geeky', 41 '104' => 'cynical', 42 '105' => 'quixotic', 43 '106' => 'crazy', 44 '107' => 'creative', 45 '108' => 'artistic', 46 '109' => 'pleased', 47 '11' => 'energetic', 48 '110' => 'bitchy', 49 '111' => 'guilty', 50 '112' => 'irritated', 51 '113' => 'blank', 52 '114' => 'apathetic', 53 '115' => 'dorky', 54 '116' => 'impressed', 55 '117' => 'naughty', 56 '118' => 'predatory', 57 '119' => 'dirty', 58 '12' => 'enraged', 59 '120' => 'giddy', 60 '121' => 'surprised', 61 '122' => 'shocked', 62 '123' => 'rejected', 63 '124' => 'numb', 64 '125' => 'cheerful', 65 '126' => 'good', 66 '127' => 'distressed', 67 '128' => 'intimidated', 68 '129' => 'crushed', 69 '13' => 'enthralled', 70 '130' => 'devious', 71 '131' => 'thankful', 72 '132' => 'grateful', 73 '133' => 'jealous', 74 '134' => 'nervous', 75 '14' => 'exhausted', 76 '15' => 'happy', 77 '16' => 'high', 78 '17' => 'horny', 79 '18' => 'hungry', 80 '19' => 'infuriated', 81 '2' => 'angry', 82 '20' => 'irate', 83 '21' => 'jubilant', 84 '22' => 'lonely', 85 '23' => 'moody', 86 '24' => 'pissed off', 87 '25' => 'sad', 88 '26' => 'satisfied', 89 '27' => 'sore', 90 '28' => 'stressed', 91 '29' => 'thirsty', 92 '3' => 'annoyed', 93 '30' => 'thoughtful', 94 '31' => 'tired', 95 '32' => 'touched', 96 '33' => 'lazy', 97 '34' => 'drunk', 98 '35' => 'ditzy', 99 '36' => 'mischievous', 100 '37' => 'morose', 101 '38' => 'gloomy', 102 '39' => 'melancholy', 103 '4' => 'anxious', 104 '40' => 'drained', 105 '41' => 'excited', 106 '42' => 'relieved', 107 '43' => 'hopeful', 108 '44' => 'amused', 109 '45' => 'determined', 110 '46' => 'scared', 111 '47' => 'frustrated', 112 '48' => 'indescribable', 113 '49' => 'sleepy', 114 '5' => 'bored', 115 '51' => 'groggy', 116 '52' => 'hyper', 117 '53' => 'relaxed', 118 '54' => 'restless', 119 '55' => 'disappointed', 120 '56' => 'curious', 121 '57' => 'mellow', 122 '58' => 'peaceful', 123 '59' => 'bouncy', 124 '6' => 'confused', 125 '60' => 'nostalgic', 126 '61' => 'okay', 127 '62' => 'rejuvenated', 128 '63' => 'complacent', 129 '64' => 'content', 130 '65' => 'indifferent', 131 '66' => 'silly', 132 '67' => 'flirty', 133 '68' => 'calm', 134 '69' => 'refreshed', 135 '7' => 'crappy', 136 '70' => 'optimistic', 137 '71' => 'pessimistic', 138 '72' => 'giggly', 139 '73' => 'pensive', 140 '74' => 'uncomfortable', 141 '75' => 'lethargic', 142 '76' => 'listless', 143 '77' => 'recumbent', 144 '78' => 'exanimate', 145 '79' => 'embarrassed', 146 '8' => 'cranky', 147 '80' => 'envious', 148 '81' => 'sympathetic', 149 '82' => 'sick', 150 '83' => 'hot', 151 '84' => 'cold', 152 '85' => 'worried', 153 '86' => 'loved', 154 '87' => 'awake', 155 '88' => 'working', 156 '89' => 'productive', 157 '9' => 'depressed', 158 '90' => 'accomplished', 159 '91' => 'busy', 160 '92' => 'blah', 161 '93' => 'full', 162 '95' => 'grumpy', 163 '96' => 'weird', 164 '97' => 'nauseated', 165 '98' => 'ecstatic', 166 '99' => 'chipper' ); 167 168 function header() { 169 echo '<div class="wrap">'; 170 screen_icon(); 171 echo '<h2>' . __( 'Import LiveJournal' ) . '</h2>'; 172 } 173 174 function footer() { 175 echo '</div>'; 176 } 177 178 function greet() { 179 ?> 180 <div class="narrow"> 181 <form action="admin.php?import=livejournal" method="post"> 182 <?php wp_nonce_field( 'lj-api-import' ) ?> 183 <?php if ( get_option( 'ljapi_username' ) && get_option( 'ljapi_password' ) ) : ?> 184 <input type="hidden" name="step" value="<?php echo esc_attr( get_option( 'ljapi_step' ) ) ?>" /> 185 <p><?php _e( 'It looks like you attempted to import your LiveJournal posts previously and got interrupted.' ) ?></p> 186 <p class="submit"> 187 <input type="submit" class="button-primary" value="<?php esc_attr_e( 'Continue previous import' ) ?>" /> 188 </p> 189 <p class="submitbox"><a href="<?php echo esc_url($_SERVER['PHP_SELF'] . '?import=livejournal&step=-1&_wpnonce=' . wp_create_nonce( 'lj-api-import' ) . '&_wp_http_referer=' . esc_attr( $_SERVER['REQUEST_URI'] )) ?>" class="deletion submitdelete"><?php _e( 'Cancel & start a new import' ) ?></a></p> 190 <p> 191 <?php else : ?> 192 <input type="hidden" name="step" value="1" /> 193 <input type="hidden" name="login" value="true" /> 194 <p><?php _e( 'Howdy! This importer allows you to connect directly to LiveJournal and download all your entries and comments' ) ?></p> 195 <p><?php _e( 'Enter your LiveJournal username and password below so we can connect to your account:' ) ?></p> 196 197 <table class="form-table"> 198 199 <tr> 200 <th scope="row"><label for="lj_username"><?php _e( 'LiveJournal Username' ) ?></label></th> 201 <td><input type="text" name="lj_username" id="lj_username" class="regular-text" /></td> 202 </tr> 203 204 <tr> 205 <th scope="row"><label for="lj_password"><?php _e( 'LiveJournal Password' ) ?></label></th> 206 <td><input type="password" name="lj_password" id="lj_password" class="regular-text" /></td> 207 </tr> 208 209 </table> 210 211 <p><?php _e( 'If you have any entries on LiveJournal which are marked as private, they will be password-protected when they are imported so that only people who know the password can see them.' ) ?></p> 212 <p><?php _e( 'If you don’t enter a password, ALL ENTRIES from your LiveJournal will be imported as public posts in WordPress.' ) ?></p> 213 <p><?php _e( 'Enter the password you would like to use for all protected entries here:' ) ?></p> 214 <table class="form-table"> 215 216 <tr> 217 <th scope="row"><label for="protected_password"><?php _e( 'Protected Post Password' ) ?></label></th> 218 <td><input type="text" name="protected_password" id="protected_password" class="regular-text" /></td> 219 </tr> 220 221 </table> 222 223 <p><?php _e( "<strong>WARNING:</strong> This can take a really long time if you have a lot of entries in your LiveJournal, or a lot of comments. Ideally, you should only start this process if you can leave your computer alone while it finishes the import." ) ?></p> 224 225 <p class="submit"> 226 <input type="submit" class="button-primary" value="<?php esc_attr_e( 'Connect to LiveJournal and Import' ) ?>" /> 227 </p> 228 229 <p><?php _e( '<strong>NOTE:</strong> If the import process is interrupted for <em>any</em> reason, come back to this page and it will continue from where it stopped automatically.' ) ?></p> 230 231 <noscript> 232 <p><?php _e( '<strong>NOTE:</strong> You appear to have JavaScript disabled, so you will need to manually click through each step of this importer. If you enable JavaScript, it will step through automatically.' ) ?></p> 233 </noscript> 234 <?php endif; ?> 235 </form> 236 </div> 237 <?php 238 } 239 240 function download_post_meta() { 241 $total = (int) get_option( 'ljapi_total' ); 242 $count = (int) get_option( 'ljapi_count' ); 243 $lastsync = get_option( 'ljapi_lastsync' ); 244 if ( !$lastsync ) { 245 update_option( 'ljapi_lastsync', '1900-01-01 00:00:00' ); 246 } 247 $sync_item_times = get_option( 'ljapi_sync_item_times' ); 248 if ( !is_array( $sync_item_times ) ) 249 $sync_item_times = array(); 250 251 do { 252 $lastsync = date( 'Y-m-d H:i:s', strtotime( get_option( 'ljapi_lastsync' ) ) ); 253 $synclist = $this->lj_ixr( 'syncitems', array( 'ver' => 1, 'lastsync' => $lastsync ) ); 254 if ( is_wp_error( $synclist ) ) 255 return $synclist; 256 257 // Keep track of if we've downloaded everything 258 $total = $synclist['total']; 259 $count = $synclist['count']; 260 261 foreach ( $synclist['syncitems'] as $event ) { 262 if ( substr( $event['item'], 0, 2 ) == 'L-' ) { 263 $sync_item_times[ str_replace( 'L-', '', $event['item'] ) ] = $event['time']; 264 if ( $event['time'] > $lastsync ) { 265 $lastsync = $event['time']; 266 update_option( 'ljapi_lastsync', $lastsync ); 267 } 268 } 269 } 270 } while ( $total > $count ); 271 // endwhile - all post meta is cached locally 272 unset( $synclist ); 273 update_option( 'ljapi_sync_item_times', $sync_item_times ); 274 update_option( 'ljapi_total', $total ); 275 update_option( 'ljapi_count', $count ); 276 277 echo '<p>' . __( 'Post metadata has been downloaded, proceeding with posts...' ) . '</p>'; 278 } 279 280 function download_post_bodies() { 281 $imported_count = (int) get_option( 'ljapi_imported_count' ); 282 $sync_item_times = get_option( 'ljapi_sync_item_times' ); 283 $lastsync = get_option( 'ljapi_lastsync_posts' ); 284 if ( !$lastsync ) 285 update_option( 'ljapi_lastsync_posts', date( 'Y-m-d H:i:s', 0 ) ); 286 287 $count = 0; 288 echo '<ol>'; 289 do { 290 $lastsync = date( 'Y-m-d H:i:s', strtotime( get_option( 'ljapi_lastsync_posts' ) ) ); 291 292 // Get the batch of items that match up with the syncitems list 293 $itemlist = $this->lj_ixr( 'getevents', array( 'ver' => 1, 294 'selecttype' => 'syncitems', 295 'lineendings' => 'pc', 296 'lastsync' => $lastsync ) ); 297 if ( is_wp_error( $itemlist ) ) 298 return $itemlist; 299 300 if ( $num = count( $itemlist['events'] ) ) { 301 for ( $e = 0; $e < count( $itemlist['events'] ); $e++ ) { 302 $event = $itemlist['events'][$e]; 303 $imported_count++; 304 $inserted = $this->import_post( $event ); 305 if ( is_wp_error( $inserted ) ) 306 return $inserted; 307 if ( $sync_item_times[ $event['itemid'] ] > $lastsync ) 308 $lastsync = $sync_item_times[ $event['itemid'] ]; 309 wp_cache_flush(); 310 } 311 update_option( 'ljapi_lastsync_posts', $lastsync ); 312 update_option( 'ljapi_imported_count', $imported_count ); 313 update_option( 'ljapi_last_sync_count', $num ); 314 } 315 $count++; 316 } while ( $num > 0 && $count < 3 ); // Doing up to 3 requests at a time to avoid memory problems 317 318 // Used so that step1 knows when to stop posting back on itself 319 update_option( 'ljapi_last_sync_count', $num ); 320 321 // Counter just used to show progress to user 322 update_option( 'ljapi_post_batch', ( (int) get_option( 'ljapi_post_batch' ) + 1 ) ); 323 324 echo '</ol>'; 325 } 326 327 function _normalize_tag( $matches ) { 328 return '<' . strtolower( $matches[1] ); 329 } 330 331 function import_post( $post ) { 332 global $wpdb; 333 334 // Make sure we haven't already imported this one 335 if ( $this->get_wp_post_ID( $post['itemid'] ) ) 336 return; 337 338 $user = wp_get_current_user(); 339 $post_author = $user->ID; 340 $post['security'] = !empty( $post['security'] ) ? $post['security'] : ''; 341 $post_status = ( 'private' == trim( $post['security'] ) ) ? 'private' : 'publish'; // Only me 342 $post_password = ( 'usemask' == trim( $post['security'] ) ) ? $this->protected_password : ''; // "Friends" via password 343 344 // For some reason, LJ sometimes sends a date as "2004-04-1408:38:00" (no space btwn date/time) 345 $post_date = $post['eventtime']; 346 if ( 18 == strlen( $post_date ) ) 347 $post_date = substr( $post_date, 0, 10 ) . ' ' . substr( $post_date, 10 ); 348 349 // Cleaning up and linking the title 350 $post_title = isset( $post['subject'] ) ? trim( $post['subject'] ) : ''; 351 $post_title = $this->translate_lj_user( $post_title ); // Translate it, but then we'll strip the link 352 $post_title = strip_tags( $post_title ); // Can't have tags in the title in WP 353 $post_title = $wpdb->escape( $post_title ); 354 355 // Clean up content 356 $post_content = $post['event']; 357 $post_content = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_content ); 358 // XHTMLize some tags 359 $post_content = str_replace( '<br>', '<br />', $post_content ); 360 $post_content = str_replace( '<hr>', '<hr />', $post_content ); 361 // lj-cut ==> <!--more--> 362 $post_content = preg_replace( '|<lj-cut text="([^"]*)">|is', '<!--more $1-->', $post_content ); 363 $post_content = str_replace( array( '<lj-cut>', '</lj-cut>' ), array( '<!--more-->', '' ), $post_content ); 364 $first = strpos( $post_content, '<!--more' ); 365 $post_content = substr( $post_content, 0, $first + 1 ) . preg_replace( '|<!--more(.*)?-->|sUi', '', substr( $post_content, $first + 1 ) ); 366 // lj-user ==> a href 367 $post_content = $this->translate_lj_user( $post_content ); 368 //$post_content = force_balance_tags( $post_content ); 369 $post_content = $wpdb->escape( $post_content ); 370 371 // Handle any tags associated with the post 372 $tags_input = !empty( $post['props']['taglist'] ) ? $post['props']['taglist'] : ''; 373 374 // Check if comments are closed on this post 375 $comment_status = !empty( $post['props']['opt_nocomments'] ) ? 'closed' : 'open'; 376 377 echo '<li>'; 378 if ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) { 379 printf( __( 'Post <strong>%s</strong> already exists.' ), stripslashes( $post_title ) ); 380 } else { 381 printf( __( 'Imported post <strong>%s</strong>...' ), stripslashes( $post_title ) ); 382 $postdata = compact( 'post_author', 'post_date', 'post_content', 'post_title', 'post_status', 'post_password', 'tags_input', 'comment_status' ); 383 $post_id = wp_insert_post( $postdata, true ); 384 if ( is_wp_error( $post_id ) ) { 385 if ( 'empty_content' == $post_id->get_error_code() ) 386 return; // Silent skip on "empty" posts 387 return $post_id; 388 } 389 if ( !$post_id ) { 390 _e( 'Couldn’t get post ID (creating post failed!)' ); 391 echo '</li>'; 392 return new WP_Error( 'insert_post_failed', __( 'Failed to create post.' ) ); 393 } 394 395 // Handle all the metadata for this post 396 $this->insert_postmeta( $post_id, $post ); 397 } 398 echo '</li>'; 399 } 400 401 // Convert lj-user tags to links to that user 402 function translate_lj_user( $str ) { 403 return preg_replace( '|<lj\s+user\s*=\s*["\']([\w-]+)["\']>|', '<a href="http://$1.livejournal.com/" class="lj-user">$1</a>', $str ); 404 } 405 406 function insert_postmeta( $post_id, $post ) { 407 // Need the original LJ id for comments 408 add_post_meta( $post_id, 'lj_itemid', $post['itemid'] ); 409 410 // And save the permalink on LJ in case we want to link back or something 411 add_post_meta( $post_id, 'lj_permalink', $post['url'] ); 412 413 // Supports the following "props" from LJ, saved as lj_<prop_name> in wp_postmeta 414 // Adult Content - adult_content 415 // Location - current_coords + current_location 416 // Mood - current_mood (translated from current_moodid) 417 // Music - current_music 418 // Userpic - picture_keyword 419 foreach ( array( 'adult_content', 'current_coords', 'current_location', 'current_moodid', 'current_music', 'picture_keyword' ) as $prop ) { 420 if ( !empty( $post['props'][$prop] ) ) { 421 if ( 'current_moodid' == $prop ) { 422 $prop = 'current_mood'; 423 $val = $this->moods[ $post['props']['current_moodid'] ]; 424 } else { 425 $val = $post['props'][$prop]; 426 } 427 add_post_meta( $post_id, 'lj_' . $prop, $val ); 428 } 429 } 430 } 431 432 // Set up a session (authenticate) with LJ 433 function get_session() { 434 // Get a session via XMLRPC 435 $cookie = $this->lj_ixr( 'sessiongenerate', array( 'ver' => 1, 'expiration' => 'short' ) ); 436 if ( is_wp_error( $cookie ) ) 437 return new WP_Error( 'cookie', __( 'Could not get a cookie from LiveJournal. Please try again soon.' ) ); 438 return new WP_Http_Cookie( array( 'name' => 'ljsession', 'value' => $cookie['ljsession'] ) ); 439 } 440 441 // Loops through and gets comment meta from LJ in batches 442 function download_comment_meta() { 443 $cookie = $this->get_session(); 444 if ( is_wp_error( $cookie ) ) 445 return $cookie; 446 447 // Load previous state (if any) 448 $this->usermap = (array) get_option( 'ljapi_usermap' ); 449 $maxid = get_option( 'ljapi_maxid' ) ? get_option( 'ljapi_maxid' ) : 1; 450 $highest_id = get_option( 'ljapi_highest_id' ) ? get_option( 'ljapi_highest_id' ) : 0; 451 452 // We need to loop over the metadata request until we have it all 453 while ( $maxid > $highest_id ) { 454 // Now get the meta listing 455 $results = wp_remote_get( $this->comments_url . '?get=comment_meta&startid=' . ( $highest_id + 1 ), 456 array( 'cookies' => array( $cookie ), 'timeout' => 20 ) ); 457 if ( is_wp_error( $results ) ) 458 return new WP_Error( 'comment_meta', __( 'Failed to retrieve comment meta information from LiveJournal. Please try again soon.' ) ); 459 460 $results = wp_remote_retrieve_body( $results ); 461 462 // Get the maxid so we know if we have them all yet 463 preg_match( '|<maxid>(\d+)</maxid>|', $results, $matches ); 464 if ( 0 == $matches[1] ) { 465 // No comment meta = no comments for this journal 466 echo '<p>' . __( 'You have no comments to import!' ) . '</p>'; 467 update_option( 'ljapi_highest_id', 1 ); 468 update_option( 'ljapi_highest_comment_id', 1 ); 469 return false; // Bail out of comment importing entirely 470 } 471 $maxid = !empty( $matches[1] ) ? $matches[1] : $maxid; 472 473 // Parse comments and get highest id available 474 preg_match_all( '|<comment id=\'(\d+)\'|is', $results, $matches ); 475 foreach ( $matches[1] as $id ) { 476 if ( $id > $highest_id ) 477 $highest_id = $id; 478 } 479 480 // Parse out the list of user mappings, and add it to the known list 481 preg_match_all( '|<usermap id=\'(\d+)\' user=\'([^\']+)\' />|', $results, $matches ); 482 foreach ( $matches[1] as $count => $userid ) 483 $this->usermap[$userid] = $matches[2][$count]; // need this in memory for translating ids => names 484 485 wp_cache_flush(); 486 } 487 // endwhile - should have seen all comment meta at this point 488 489 update_option( 'ljapi_usermap', $this->usermap ); 490 update_option( 'ljapi_maxid', $maxid ); 491 update_option( 'ljapi_highest_id', $highest_id ); 492 493 echo '<p>' . __( ' Comment metadata downloaded successfully, proceeding with comment bodies...' ) . '</p>'; 494 495 return true; 496 } 497 498 // Downloads actual comment bodies from LJ 499 // Inserts them all directly to the DB, with additional info stored in "spare" fields 500 function download_comment_bodies() { 501 global $wpdb; 502 $cookie = $this->get_session(); 503 if ( is_wp_error( $cookie ) ) 504 return $cookie; 505 506 // Load previous state (if any) 507 $this->usermap = (array) get_option( 'ljapi_usermap' ); 508 $maxid = get_option( 'ljapi_maxid' ) ? (int) get_option( 'ljapi_maxid' ) : 1; 509 $highest_id = (int) get_option( 'ljapi_highest_comment_id' ); 510 $loop = 0; 511 while ( $maxid > $highest_id && $loop < 5 ) { // We do 5 loops per call to avoid memory limits 512 $loop++; 513 514 // Get a batch of comments, using the highest_id we've already got as a starting point 515 $results = wp_remote_get( $this->comments_url . '?get=comment_body&startid=' . ( $highest_id + 1 ), 516 array( 'cookies' => array( $cookie ), 'timeout' => 20 ) ); 517 if ( is_wp_error( $results ) ) 518 return new WP_Error( 'comment_bodies', __( 'Failed to retrieve comment bodies from LiveJournal. Please try again soon.' ) ); 519 520 $results = wp_remote_retrieve_body( $results ); 521 522 // Parse out each comment and insert directly 523 preg_match_all( '|<comment id=\'(\d+)\'.*</comment>|iUs', $results, $matches ); 524 for ( $c = 0; $c < count( $matches[0] ); $c++ ) { 525 // Keep track of highest id seen 526 if ( $matches[1][$c] > $highest_id ) { 527 $highest_id = $matches[1][$c]; 528 update_option( 'ljapi_highest_comment_id', $highest_id ); 529 } 530 531 $comment = $matches[0][$c]; 532 533 // Filter out any captured, deleted comments (nothing useful to import) 534 $comment = preg_replace( '|<comment id=\'\d+\' jitemid=\'\d+\' posterid=\'\d+\' state=\'D\'[^/]*/>|is', '', $comment ); 535 536 // Parse this comment into an array and insert 537 $comment = $this->parse_comment( $comment ); 538 $comment = wp_filter_comment( $comment ); 539 $id = wp_insert_comment( $comment ); 540 541 // Clear cache 542 clean_comment_cache( $id ); 543 } 544 545 // Clear cache to preseve memory 546 wp_cache_flush(); 547 } 548 // endwhile - all comments downloaded and ready for bulk processing 549 550 // Counter just used to show progress to user 551 update_option( 'ljapi_comment_batch', ( (int) get_option( 'ljapi_comment_batch' ) + 1 ) ); 552 553 return true; 554 } 555 556 // Takes a block of XML and parses out all the elements of the comment 557 function parse_comment( $comment ) { 558 global $wpdb; 559 560 // Get the top-level attributes 561 preg_match( '|<comment([^>]+)>|i', $comment, $attribs ); 562 preg_match( '| id=\'(\d+)\'|i', $attribs[1], $matches ); 563 $lj_comment_ID = $matches[1]; 564 preg_match( '| jitemid=\'(\d+)\'|i', $attribs[1], $matches ); 565 $lj_comment_post_ID = $matches[1]; 566 preg_match( '| posterid=\'(\d+)\'|i', $attribs[1], $matches ); 567 $comment_author_ID = isset( $matches[1] ) ? $matches[1] : 0; 568 preg_match( '| parentid=\'(\d+)\'|i', $attribs[1], $matches ); // optional 569 $lj_comment_parent = isset( $matches[1] ) ? $matches[1] : 0; 570 preg_match( '| state=\'([SDFA])\'|i', $attribs[1], $matches ); // optional 571 $lj_comment_state = isset( $matches[1] ) ? $matches[1] : 'A'; 572 573 // Clean up "subject" - this will become the first line of the comment in WP 574 preg_match( '|<subject>(.*)</subject>|is', $comment, $matches ); 575 if ( isset( $matches[1] ) ) { 576 $comment_subject = $wpdb->escape( trim( $matches[1] ) ); 577 if ( 'Re:' == $comment_subject ) 578 $comment_subject = ''; 579 } 580 581 // Get the body and HTMLize it 582 preg_match( '|<body>(.*)</body>|is', $comment, $matches ); 583 $comment_content = !empty( $comment_subject ) ? $comment_subject . "\n\n" . $matches[1] : $matches[1]; 584 $comment_content = @html_entity_decode( $comment_content, ENT_COMPAT, get_option('blog_charset') ); 585 $comment_content = str_replace( ''', "'", $comment_content ); 586 $comment_content = wpautop( $comment_content ); 587 $comment_content = str_replace( '<br>', '<br />', $comment_content ); 588 $comment_content = str_replace( '<hr>', '<hr />', $comment_content ); 589 $comment_content = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $comment_content ); 590 $comment_content = $wpdb->escape( trim( $comment_content ) ); 591 592 // Get and convert the date 593 preg_match( '|<date>(.*)</date>|i', $comment, $matches ); 594 $comment_date = trim( str_replace( array( 'T', 'Z' ), ' ', $matches[1] ) ); 595 596 // Grab IP if available 597 preg_match( '|<property name=\'poster_ip\'>(.*)</property>|i', $comment, $matches ); // optional 598 $comment_author_IP = isset( $matches[1] ) ? $matches[1] : ''; 599 600 // Try to get something useful for the comment author, especially if it was "my" comment 601 $author = ( empty( $comment_author_ID ) || empty( $this->usermap[$comment_author_ID] ) || substr( $this->usermap[$comment_author_ID], 0, 4 ) == 'ext_' ) ? __( 'Anonymous' ) : $this->usermap[$comment_author_ID]; 602 if ( get_option( 'ljapi_username' ) == $author ) { 603 $user = wp_get_current_user(); 604 $user_id = $user->ID; 605 $author = $user->display_name; 606 $url = trailingslashit( get_option( 'home' ) ); 607 } else { 608 $user_id = 0; 609 $url = ( __( 'Anonymous' ) == $author ) ? '' : 'http://' . $author . '.livejournal.com/'; 610 } 611 612 // Send back the array of details 613 return array( 'lj_comment_ID' => $lj_comment_ID, 614 'lj_comment_post_ID' => $lj_comment_post_ID, 615 'lj_comment_parent' => ( !empty( $lj_comment_parent ) ? $lj_comment_parent : 0 ), 616 'lj_comment_state' => $lj_comment_state, 617 'comment_post_ID' => $this->get_wp_post_ID( $lj_comment_post_ID ), 618 'comment_author' => $author, 619 'comment_author_url' => $url, 620 'comment_author_email' => '', 621 'comment_content' => $comment_content, 622 'comment_date' => $comment_date, 623 'comment_author_IP' => ( !empty( $comment_author_IP ) ? $comment_author_IP : '' ), 624 'comment_approved' => ( in_array( $lj_comment_state, array( 'A', 'F' ) ) ? 1 : 0 ), 625 'comment_karma' => $lj_comment_ID, // Need this and next value until rethreading is done 626 'comment_agent' => $lj_comment_parent, 627 'comment_type' => 'livejournal', // Custom type, so we can find it later for processing 628 'user_ID' => $user_id 629 ); 630 } 631 632 633 // Gets the post_ID that a LJ post has been saved as within WP 634 function get_wp_post_ID( $post ) { 635 global $wpdb; 636 637 if ( empty( $this->postmap[$post] ) ) 638 $this->postmap[$post] = (int) $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'lj_itemid' AND meta_value = %d", $post ) ); 639 640 return $this->postmap[$post]; 641 } 642 643 // Gets the comment_ID that a LJ comment has been saved as within WP 644 function get_wp_comment_ID( $comment ) { 645 global $wpdb; 646 if ( empty( $this->commentmap[$comment] ) ) 647 $this->commentmap[$comment] = $wpdb->get_var( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_karma = %d", $comment ) ); 648 return $this->commentmap[$comment]; 649 } 650 651 function lj_ixr() { 652 if ( $challenge = $this->ixr->query( 'LJ.XMLRPC.getchallenge' ) ) { 653 $challenge = $this->ixr->getResponse(); 654 } 655 if ( isset( $challenge['challenge'] ) ) { 656 $params = array( 'username' => $this->username, 657 'auth_method' => 'challenge', 658 'auth_challenge' => $challenge['challenge'], 659 'auth_response' => md5( $challenge['challenge'] . md5( $this->password ) ) ); 660 } else { 661 return new WP_Error( 'IXR', __( 'LiveJournal is not responding to authentication requests. Please wait a while and then try again.' ) ); 662 } 663 664 $args = func_get_args(); 665 $method = array_shift( $args ); 666 if ( isset( $args[0] ) ) 667 $params = array_merge( $params, $args[0] ); 668 if ( $this->ixr->query( 'LJ.XMLRPC.' . $method, $params ) ) { 669 return $this->ixr->getResponse(); 670 } else { 671 return new WP_Error( 'IXR', __( 'XML-RPC Request Failed -- ' ) . $this->ixr->getErrorCode() . ': ' . $this->ixr->getErrorMessage() ); 672 } 673 } 674 675 function dispatch() { 676 if ( empty( $_REQUEST['step'] ) ) 677 $step = 0; 678 else 679 $step = (int) $_REQUEST['step']; 680 681 $this->header(); 682 683 switch ( $step ) { 684 case -1 : 685 $this->cleanup(); 686 // Intentional no break 687 case 0 : 688 $this->greet(); 689 break; 690 case 1 : 691 case 2 : 692 case 3 : 693 check_admin_referer( 'lj-api-import' ); 694 $result = $this->{ 'step' . $step }(); 695 if ( is_wp_error( $result ) ) { 696 $this->throw_error( $result, $step ); 697 } 698 break; 699 } 700 701 $this->footer(); 702 } 703 704 // Technically the first half of step 1, this is separated to allow for AJAX 705 // calls. Sets up some variables and options and confirms authentication. 706 function setup() { 707 global $verified; 708 // Get details from form or from DB 709 if ( !empty( $_POST['lj_username'] ) && !empty( $_POST['lj_password'] ) ) { 710 // Store details for later 711 $this->username = $_POST['lj_username']; 712 $this->password = $_POST['lj_password']; 713 update_option( 'ljapi_username', $this->username ); 714 update_option( 'ljapi_password', $this->password ); 715 } else { 716 $this->username = get_option( 'ljapi_username' ); 717 $this->password = get_option( 'ljapi_password' ); 718 } 719 720 // This is the password to set on protected posts 721 if ( !empty( $_POST['protected_password'] ) ) { 722 $this->protected_password = $_POST['protected_password']; 723 update_option( 'ljapi_protected_password', $this->protected_password ); 724 } else { 725 $this->protected_password = get_option( 'ljapi_protected_password' ); 726 } 727 728 // Log in to confirm the details are correct 729 if ( empty( $this->username ) || empty( $this->password ) ) { 730 ?> 731 <p><?php _e( 'Please enter your LiveJournal username <em>and</em> password so we can download your posts and comments.' ) ?></p> 732 <p><a href="<?php echo esc_url($_SERVER['PHP_SELF'] . '?import=livejournal&step=-1&_wpnonce=' . wp_create_nonce( 'lj-api-import' ) . '&_wp_http_referer=' . esc_attr( str_replace( '&step=1', '', $_SERVER['REQUEST_URI'] ) ) ) ?>"><?php _e( 'Start again' ) ?></a></p> 733 <?php 734 return false; 735 } 736 $verified = $this->lj_ixr( 'login' ); 737 if ( is_wp_error( $verified ) ) { 738 if ( 100 == $this->ixr->getErrorCode() || 101 == $this->ixr->getErrorCode() ) { 739 delete_option( 'ljapi_username' ); 740 delete_option( 'ljapi_password' ); 741 delete_option( 'ljapi_protected_password' ); 742 ?> 743 <p><?php _e( 'Logging in to LiveJournal failed. Check your username and password and try again.' ) ?></p> 744 <p><a href="<?php echo esc_url($_SERVER['PHP_SELF'] . '?import=livejournal&step=-1&_wpnonce=' . wp_create_nonce( 'lj-api-import' ) . '&_wp_http_referer=' . esc_attr( str_replace( '&step=1', '', $_SERVER['REQUEST_URI'] ) ) ) ?>"><?php _e( 'Start again' ) ?></a></p> 745 <?php 746 return false; 747 } else { 748 return $verified; 749 } 750 } else { 751 update_option( 'ljapi_verified', 'yes' ); 752 } 753 754 // Set up some options to avoid them autoloading (these ones get big) 755 add_option( 'ljapi_sync_item_times', '', '', 'no' ); 756 add_option( 'ljapi_usermap', '', '', 'no' ); 757 update_option( 'ljapi_comment_batch', 0 ); 758 759 return true; 760 } 761 762 // Check form inputs and start importing posts 763 function step1() { 764 global $verified; 765 set_time_limit( 0 ); 766 update_option( 'ljapi_step', 1 ); 767 if ( !$this->ixr ) $this->ixr = new IXR_Client( $this->ixr_url, false, 80, 30 ); 768 if ( empty( $_POST['login'] ) ) { 769 // We're looping -- load some details from DB 770 $this->username = get_option( 'ljapi_username' ); 771 $this->password = get_option( 'ljapi_password' ); 772 $this->protected_password = get_option( 'ljapi_protected_password' ); 773 } else { 774 // First run (non-AJAX) 775 $setup = $this->setup(); 776 if ( !$setup ) { 777 return false; 778 } else if ( is_wp_error( $setup ) ) { 779 $this->throw_error( $setup, 1 ); 780 return false; 781 } 782 } 783 784 echo '<div id="ljapi-status">'; 785 echo '<h3>' . __( 'Importing Posts' ) . '</h3>'; 786 echo '<p>' . __( 'We’re downloading and importing your LiveJournal posts...' ) . '</p>'; 787 if ( get_option( 'ljapi_post_batch' ) && count( get_option( 'ljapi_sync_item_times' ) ) ) { 788 $batch = count( get_option( 'ljapi_sync_item_times' ) ); 789 $batch = $count > 300 ? ceil( $batch / 300 ) : 1; 790 echo '<p><strong>' . sprintf( __( 'Imported post batch %d of <strong>approximately</strong> %d' ), ( get_option( 'ljapi_post_batch' ) + 1 ), $batch ) . '</strong></p>'; 791 } 792 ob_flush(); flush(); 793 794 if ( !get_option( 'ljapi_lastsync' ) || '1900-01-01 00:00:00' == get_option( 'ljapi_lastsync' ) ) { 795 // We haven't downloaded meta yet, so do that first 796 $result = $this->download_post_meta(); 797 if ( is_wp_error( $result ) ) { 798 $this->throw_error( $result, 1 ); 799 return false; 800 } 801 } 802 803 // Download a batch of actual posts 804 $result = $this->download_post_bodies(); 805 if ( is_wp_error( $result ) ) { 806 if ( 406 == $this->ixr->getErrorCode() ) { 807 ?> 808 <p><strong><?php _e( 'Uh oh – LiveJournal has disconnected us because we made too many requests to their servers too quickly.' ) ?></strong></p> 809 <p><strong><?php _e( 'We’ve saved where you were up to though, so if you come back to this importer in about 30 minutes, you should be able to continue from where you were.' ) ?></strong></p> 810 <?php 811 echo $this->next_step( 1, __( 'Try Again' ) ); 812 return false; 813 } else { 814 $this->throw_error( $result, 1 ); 815 return false; 816 } 817 } 818 819 if ( get_option( 'ljapi_last_sync_count' ) > 0 ) { 820 ?> 821 <form action="admin.php?import=livejournal" method="post" id="ljapi-auto-repost"> 822 <?php wp_nonce_field( 'lj-api-import' ) ?> 823 <input type="hidden" name="step" id="step" value="1" /> 824 <p><input type="submit" class="button-primary" value="<?php esc_attr_e( 'Import the next batch' ) ?>" /> <span id="auto-message"></span></p> 825 </form> 826 <?php $this->auto_ajax( 'ljapi-auto-repost', 'auto-message', 0 ); ?> 827 <?php 828 } else { 829 echo '<p>' . __( 'Your posts have all been imported, but wait – there’s more! Now we need to download & import your comments.' ) . '</p>'; 830 echo $this->next_step( 2, __( 'Download my comments »' ) ); 831 $this->auto_submit(); 832 } 833 echo '</div>'; 834 } 835 836 // Download comments to local XML 837 function step2() { 838 set_time_limit( 0 ); 839 update_option( 'ljapi_step', 2 ); 840 $this->username = get_option( 'ljapi_username' ); 841 $this->password = get_option( 'ljapi_password' ); 842 $this->ixr = new IXR_Client( $this->ixr_url, false, 80, 30 ); 843 844 echo '<div id="ljapi-status">'; 845 echo '<h3>' . __( 'Downloading Comments' ) . '</h3>'; 846 echo '<p>' . __( 'Now we will download your comments so we can import them (this could take a <strong>long</strong> time if you have lots of comments)...' ) . '</p>'; 847 ob_flush(); flush(); 848 849 if ( !get_option( 'ljapi_usermap' ) ) { 850 // We haven't downloaded meta yet, so do that first 851 $result = $this->download_comment_meta(); 852 if ( is_wp_error( $result ) ) { 853 $this->throw_error( $result, 2 ); 854 return false; 855 } 856 } 857 858 // Download a batch of actual comments 859 $result = $this->download_comment_bodies(); 860 if ( is_wp_error( $result ) ) { 861 $this->throw_error( $result, 2 ); 862 return false; 863 } 864 865 $maxid = get_option( 'ljapi_maxid' ) ? (int) get_option( 'ljapi_maxid' ) : 1; 866 $highest_id = (int) get_option( 'ljapi_highest_comment_id' ); 867 if ( $maxid > $highest_id ) { 868 $batch = $maxid > 5000 ? ceil( $maxid / 5000 ) : 1; 869 ?> 870 <form action="admin.php?import=livejournal" method="post" id="ljapi-auto-repost"> 871 <p><strong><?php printf( __( 'Imported comment batch %d of <strong>approximately</strong> %d' ), get_option( 'ljapi_comment_batch' ), $batch ) ?></strong></p> 872 <?php wp_nonce_field( 'lj-api-import' ) ?> 873 <input type="hidden" name="step" id="step" value="2" /> 874 <p><input type="submit" class="button-primary" value="<?php esc_attr_e( 'Import the next batch' ) ?>" /> <span id="auto-message"></span></p> 875 </form> 876 <?php $this->auto_ajax( 'ljapi-auto-repost', 'auto-message', 0 ); ?> 877 <?php 878 } else { 879 echo '<p>' . __( 'Your comments have all been imported now, but we still need to rebuild your conversation threads.' ) . '</p>'; 880 echo $this->next_step( 3, __( 'Rebuild my comment threads »' ) ); 881 $this->auto_submit(); 882 } 883 echo '</div>'; 884 } 885 886 // Re-thread comments already in the DB 887 function step3() { 888 global $wpdb; 889 set_time_limit( 0 ); 890 update_option( 'ljapi_step', 3 ); 891 892 echo '<div id="ljapi-status">'; 893 echo '<h3>' . __( 'Threading Comments' ) . '</h3>'; 894 echo '<p>' . __( 'We are now re-building the threading of your comments (this can also take a while if you have lots of comments)...' ) . '</p>'; 895 ob_flush(); flush(); 896 897 // Only bother adding indexes if they have over 5000 comments (arbitrary number) 898 $imported_comments = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_type = 'livejournal'" ); 899 $added_indices = false; 900 if ( 5000 < $imported_comments ) { 901 include_once (ABSPATH . 'wp-admin/includes/upgrade.php'); 902 $added_indices = true; 903 add_clean_index( $wpdb->comments, 'comment_type' ); 904 add_clean_index( $wpdb->comments, 'comment_karma' ); 905 add_clean_index( $wpdb->comments, 'comment_agent' ); 906 } 907 908 // Get LJ comments, which haven't been threaded yet, 5000 at a time and thread them 909 while ( $comments = $wpdb->get_results( "SELECT comment_ID, comment_agent FROM {$wpdb->comments} WHERE comment_type = 'livejournal' AND comment_agent != '0' LIMIT 5000", OBJECT ) ) { 910 foreach ( $comments as $comment ) { 911 $wpdb->update( $wpdb->comments, 912 array( 'comment_parent' => $this->get_wp_comment_ID( $comment->comment_agent ), 'comment_type' => 'livejournal-done' ), 913 array( 'comment_ID' => $comment->comment_ID ) ); 914 } 915 wp_cache_flush(); 916 $wpdb->flush(); 917 } 918 919 // Revert the comments table back to normal and optimize it to reclaim space 920 if ( $added_indices ) { 921 drop_index( $wpdb->comments, 'comment_type' ); 922 drop_index( $wpdb->comments, 'comment_karma' ); 923 drop_index( $wpdb->comments, 'comment_agent' ); 924 $wpdb->query( "OPTIMIZE TABLE {$wpdb->comments}" ); 925 } 926 927 // Clean up database and we're out 928 $this->cleanup(); 929 do_action( 'import_done', 'livejournal' ); 930 if ( $imported_comments > 1 ) 931 echo '<p>' . sprintf( __( "Successfully re-threaded %s comments." ), number_format( $imported_comments ) ) . '</p>'; 932 echo '<h3>'; 933 printf( __( 'All done. <a href="%s">Have fun!</a>' ), get_option( 'home' ) ); 934 echo '</h3>'; 935 echo '</div>'; 936 } 937 938 // Output an error message with a button to try again. 939 function throw_error( $error, $step ) { 940 echo '<p><strong>' . $error->get_error_message() . '</strong></p>'; 941 echo $this->next_step( $step, __( 'Try Again' ) ); 942 } 943 944 // Returns the HTML for a link to the next page 945 function next_step( $next_step, $label, $id = 'ljapi-next-form' ) { 946 $str = '<form action="admin.php?import=livejournal" method="post" id="' . $id . '">'; 947 $str .= wp_nonce_field( 'lj-api-import', '_wpnonce', true, false ); 948 $str .= wp_referer_field( false ); 949 $str .= '<input type="hidden" name="step" id="step" value="' . esc_attr($next_step) . '" />'; 950 $str .= '<p><input type="submit" class="button-primary" value="' . esc_attr( $label ) . '" /> <span id="auto-message"></span></p>'; 951 $str .= '</form>'; 952 953 return $str; 954 } 955 956 // Automatically submit the specified form after $seconds 957 // Include a friendly countdown in the element with id=$msg 958 function auto_submit( $id = 'ljapi-next-form', $msg = 'auto-message', $seconds = 10 ) { 959 ?><script type="text/javascript"> 960 next_counter = <?php echo $seconds ?>; 961 jQuery(document).ready(function(){ 962 ljapi_msg(); 963 }); 964 965 function ljapi_msg() { 966 str = '<?php _e( "Continuing in %d" ) ?>'; 967 jQuery( '#<?php echo $msg ?>' ).text( str.replace( /%d/, next_counter ) ); 968 if ( next_counter <= 0 ) { 969 if ( jQuery( '#<?php echo $id ?>' ).length ) { 970 jQuery( "#<?php echo $id ?> input[type='submit']" ).hide(); 971 str = '<?php _e( "Continuing" ) ?> <img src="images/wpspin_light.gif" alt="" id="processing" align="top" />'; 972 jQuery( '#<?php echo $msg ?>' ).html( str ); 973 jQuery( '#<?php echo $id ?>' ).submit(); 974 return; 975 } 976 } 977 next_counter = next_counter - 1; 978 setTimeout('ljapi_msg()', 1000); 979 } 980 </script><?php 981 } 982 983 // Automatically submit the form with #id to continue the process 984 // Hide any submit buttons to avoid people clicking them 985 // Display a countdown in the element indicated by $msg for "Continuing in x" 986 function auto_ajax( $id = 'ljapi-next-form', $msg = 'auto-message', $seconds = 5 ) { 987 ?><script type="text/javascript"> 988 next_counter = <?php echo $seconds ?>; 989 jQuery(document).ready(function(){ 990 ljapi_msg(); 991 }); 992 993 function ljapi_msg() { 994 str = '<?php _e( "Continuing in %d" ) ?>'; 995 jQuery( '#<?php echo $msg ?>' ).text( str.replace( /%d/, next_counter ) ); 996 if ( next_counter <= 0 ) { 997 if ( jQuery( '#<?php echo $id ?>' ).length ) { 998 jQuery( "#<?php echo $id ?> input[type='submit']" ).hide(); 999 jQuery.ajaxSetup({'timeout':3600000}); 1000 str = '<?php _e( "Processing next batch." ) ?> <img src="images/wpspin_light.gif" alt="" id="processing" align="top" />'; 1001 jQuery( '#<?php echo $msg ?>' ).html( str ); 1002 jQuery('#ljapi-status').load(ajaxurl, {'action':'lj-importer', 1003 'step':jQuery('#step').val(), 1004 '_wpnonce':'<?php echo wp_create_nonce( 'lj-api-import' ) ?>', 1005 '_wp_http_referer':'<?php echo $_SERVER['REQUEST_URI'] ?>'}); 1006 return; 1007 } 1008 } 1009 next_counter = next_counter - 1; 1010 setTimeout('ljapi_msg()', 1000); 1011 } 1012 </script><?php 1013 } 1014 1015 // Remove all options used during import process and 1016 // set wp_comments entries back to "normal" values 1017 function cleanup() { 1018 global $wpdb; 1019 1020 delete_option( 'ljapi_username' ); 1021 delete_option( 'ljapi_password' ); 1022 delete_option( 'ljapi_protected_password' ); 1023 delete_option( 'ljapi_verified' ); 1024 delete_option( 'ljapi_total' ); 1025 delete_option( 'ljapi_count' ); 1026 delete_option( 'ljapi_lastsync' ); 1027 delete_option( 'ljapi_last_sync_count' ); 1028 delete_option( 'ljapi_sync_item_times' ); 1029 delete_option( 'ljapi_lastsync_posts' ); 1030 delete_option( 'ljapi_post_batch' ); 1031 delete_option( 'ljapi_imported_count' ); 1032 delete_option( 'ljapi_maxid' ); 1033 delete_option( 'ljapi_usermap' ); 1034 delete_option( 'ljapi_highest_id' ); 1035 delete_option( 'ljapi_highest_comment_id' ); 1036 delete_option( 'ljapi_comment_batch' ); 1037 delete_option( 'ljapi_step' ); 1038 1039 $wpdb->update( $wpdb->comments, 1040 array( 'comment_karma' => 0, 'comment_agent' => 'WP LJ Importer', 'comment_type' => '' ), 1041 array( 'comment_type' => 'livejournal-done' ) ); 1042 $wpdb->update( $wpdb->comments, 1043 array( 'comment_karma' => 0, 'comment_agent' => 'WP LJ Importer', 'comment_type' => '' ), 1044 array( 'comment_type' => 'livejournal' ) ); 1045 } 1046 1047 function LJ_API_Import() { 1048 $this->__construct(); 1049 } 1050 1051 function __construct() { 1052 // Nothing 1053 } 1054 } 1055 1056 $lj_api_import = new LJ_API_Import(); 1057 1058 register_importer( 'livejournal', __( 'LiveJournal' ), __( 'Import posts from LiveJournal using their API.' ), array( $lj_api_import, 'dispatch' ) ); 1059 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Fri Jan 8 00:19:48 2010 | Cross-referenced by PHPXref 0.7 |