diff -r a4642baaf829 -r 4d4862461b8d web/wp-admin/import/livejournal.php --- a/web/wp-admin/import/livejournal.php Tue Feb 02 14:45:47 2010 +0000 +++ b/web/wp-admin/import/livejournal.php Tue Feb 02 15:44:16 2010 +0000 @@ -1,1054 +1,1059 @@ - 'aggravated', - '10' => 'discontent', - '100' => 'rushed', - '101' => 'contemplative', - '102' => 'nerdy', - '103' => 'geeky', - '104' => 'cynical', - '105' => 'quixotic', - '106' => 'crazy', - '107' => 'creative', - '108' => 'artistic', - '109' => 'pleased', - '11' => 'energetic', - '110' => 'bitchy', - '111' => 'guilty', - '112' => 'irritated', - '113' => 'blank', - '114' => 'apathetic', - '115' => 'dorky', - '116' => 'impressed', - '117' => 'naughty', - '118' => 'predatory', - '119' => 'dirty', - '12' => 'enraged', - '120' => 'giddy', - '121' => 'surprised', - '122' => 'shocked', - '123' => 'rejected', - '124' => 'numb', - '125' => 'cheerful', - '126' => 'good', - '127' => 'distressed', - '128' => 'intimidated', - '129' => 'crushed', - '13' => 'enthralled', - '130' => 'devious', - '131' => 'thankful', - '132' => 'grateful', - '133' => 'jealous', - '134' => 'nervous', - '14' => 'exhausted', - '15' => 'happy', - '16' => 'high', - '17' => 'horny', - '18' => 'hungry', - '19' => 'infuriated', - '2' => 'angry', - '20' => 'irate', - '21' => 'jubilant', - '22' => 'lonely', - '23' => 'moody', - '24' => 'pissed off', - '25' => 'sad', - '26' => 'satisfied', - '27' => 'sore', - '28' => 'stressed', - '29' => 'thirsty', - '3' => 'annoyed', - '30' => 'thoughtful', - '31' => 'tired', - '32' => 'touched', - '33' => 'lazy', - '34' => 'drunk', - '35' => 'ditzy', - '36' => 'mischievous', - '37' => 'morose', - '38' => 'gloomy', - '39' => 'melancholy', - '4' => 'anxious', - '40' => 'drained', - '41' => 'excited', - '42' => 'relieved', - '43' => 'hopeful', - '44' => 'amused', - '45' => 'determined', - '46' => 'scared', - '47' => 'frustrated', - '48' => 'indescribable', - '49' => 'sleepy', - '5' => 'bored', - '51' => 'groggy', - '52' => 'hyper', - '53' => 'relaxed', - '54' => 'restless', - '55' => 'disappointed', - '56' => 'curious', - '57' => 'mellow', - '58' => 'peaceful', - '59' => 'bouncy', - '6' => 'confused', - '60' => 'nostalgic', - '61' => 'okay', - '62' => 'rejuvenated', - '63' => 'complacent', - '64' => 'content', - '65' => 'indifferent', - '66' => 'silly', - '67' => 'flirty', - '68' => 'calm', - '69' => 'refreshed', - '7' => 'crappy', - '70' => 'optimistic', - '71' => 'pessimistic', - '72' => 'giggly', - '73' => 'pensive', - '74' => 'uncomfortable', - '75' => 'lethargic', - '76' => 'listless', - '77' => 'recumbent', - '78' => 'exanimate', - '79' => 'embarrassed', - '8' => 'cranky', - '80' => 'envious', - '81' => 'sympathetic', - '82' => 'sick', - '83' => 'hot', - '84' => 'cold', - '85' => 'worried', - '86' => 'loved', - '87' => 'awake', - '88' => 'working', - '89' => 'productive', - '9' => 'depressed', - '90' => 'accomplished', - '91' => 'busy', - '92' => 'blah', - '93' => 'full', - '95' => 'grumpy', - '96' => 'weird', - '97' => 'nauseated', - '98' => 'ecstatic', - '99' => 'chipper' ); - - function header() { - echo '
'; - screen_icon(); - echo '

' . __( 'Import LiveJournal' ) . '

'; - } - - function footer() { - echo '
'; - } - - function greet() { - ?> -
-
- - - -

-

- -

-

-

- - - -

-

- - - - - - - - - - - - - -
- -

-

-

- - - - - - - -
- -

WARNING: 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." ) ?>

- -

- -

- -

NOTE: If the import process is interrupted for any reason, come back to this page and it will continue from where it stopped automatically.' ) ?>

- - - -
-
- lj_ixr( 'syncitems', array( 'ver' => 1, 'lastsync' => $lastsync ) ); - if ( is_wp_error( $synclist ) ) - return $synclist; - - // Keep track of if we've downloaded everything - $total = $synclist['total']; - $count = $synclist['count']; - - foreach ( $synclist['syncitems'] as $event ) { - if ( substr( $event['item'], 0, 2 ) == 'L-' ) { - $sync_item_times[ str_replace( 'L-', '', $event['item'] ) ] = $event['time']; - if ( $event['time'] > $lastsync ) { - $lastsync = $event['time']; - update_option( 'ljapi_lastsync', $lastsync ); - } - } - } - } while ( $total > $count ); - // endwhile - all post meta is cached locally - unset( $synclist ); - update_option( 'ljapi_sync_item_times', $sync_item_times ); - update_option( 'ljapi_total', $total ); - update_option( 'ljapi_count', $count ); - - echo '

' . __( 'Post metadata has been downloaded, proceeding with posts...' ) . '

'; - } - - function download_post_bodies() { - $imported_count = (int) get_option( 'ljapi_imported_count' ); - $sync_item_times = get_option( 'ljapi_sync_item_times' ); - $lastsync = get_option( 'ljapi_lastsync_posts' ); - if ( !$lastsync ) - update_option( 'ljapi_lastsync_posts', date( 'Y-m-d H:i:s', 0 ) ); - - $count = 0; - echo '
    '; - do { - $lastsync = date( 'Y-m-d H:i:s', strtotime( get_option( 'ljapi_lastsync_posts' ) ) ); - - // Get the batch of items that match up with the syncitems list - $itemlist = $this->lj_ixr( 'getevents', array( 'ver' => 1, - 'selecttype' => 'syncitems', - 'lineendings' => 'pc', - 'lastsync' => $lastsync ) ); - if ( is_wp_error( $itemlist ) ) - return $itemlist; - - if ( $num = count( $itemlist['events'] ) ) { - for ( $e = 0; $e < count( $itemlist['events'] ); $e++ ) { - $event = $itemlist['events'][$e]; - $imported_count++; - $inserted = $this->import_post( $event ); - if ( is_wp_error( $inserted ) ) - return $inserted; - if ( $sync_item_times[ $event['itemid'] ] > $lastsync ) - $lastsync = $sync_item_times[ $event['itemid'] ]; - wp_cache_flush(); - } - update_option( 'ljapi_lastsync_posts', $lastsync ); - update_option( 'ljapi_imported_count', $imported_count ); - update_option( 'ljapi_last_sync_count', $num ); - } - $count++; - } while ( $num > 0 && $count < 3 ); // Doing up to 3 requests at a time to avoid memory problems - - // Used so that step1 knows when to stop posting back on itself - update_option( 'ljapi_last_sync_count', $num ); - - // Counter just used to show progress to user - update_option( 'ljapi_post_batch', ( (int) get_option( 'ljapi_post_batch' ) + 1 ) ); - - echo '
'; - } - - function import_post( $post ) { - global $wpdb; - - // Make sure we haven't already imported this one - if ( $this->get_wp_post_ID( $post['itemid'] ) ) - return; - - $user = wp_get_current_user(); - $post_author = $user->ID; - $post['security'] = !empty( $post['security'] ) ? $post['security'] : ''; - $post_status = ( 'private' == trim( $post['security'] ) ) ? 'private' : 'publish'; // Only me - $post_password = ( 'usemask' == trim( $post['security'] ) ) ? $this->protected_password : ''; // "Friends" via password - - // For some reason, LJ sometimes sends a date as "2004-04-1408:38:00" (no space btwn date/time) - $post_date = $post['eventtime']; - if ( 18 == strlen( $post_date ) ) - $post_date = substr( $post_date, 0, 10 ) . ' ' . substr( $post_date, 10 ); - - // Cleaning up and linking the title - $post_title = isset( $post['subject'] ) ? trim( $post['subject'] ) : ''; - $post_title = $this->translate_lj_user( $post_title ); // Translate it, but then we'll strip the link - $post_title = strip_tags( $post_title ); // Can't have tags in the title in WP - $post_title = $wpdb->escape( $post_title ); - - // Clean up content - $post_content = $post['event']; - $post_content = preg_replace_callback( '|<(/?[A-Z]+)|', create_function( '$match', 'return "<" . strtolower( $match[1] );' ), $post_content ); - // XHTMLize some tags - $post_content = str_replace( '
', '
', $post_content ); - $post_content = str_replace( '
', '
', $post_content ); - // lj-cut ==> - $post_content = preg_replace( '||is', '', $post_content ); - $post_content = str_replace( array( '', '' ), array( '', '' ), $post_content ); - $first = strpos( $post_content, '|sUi', '', substr( $post_content, $first + 1 ) ); - // lj-user ==> a href - $post_content = $this->translate_lj_user( $post_content ); - //$post_content = force_balance_tags( $post_content ); - $post_content = $wpdb->escape( $post_content ); - - // Handle any tags associated with the post - $tags_input = !empty( $post['props']['taglist'] ) ? $post['props']['taglist'] : ''; - - // Check if comments are closed on this post - $comment_status = !empty( $post['props']['opt_nocomments'] ) ? 'closed' : 'open'; - - echo '
  • '; - if ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) { - printf( __( 'Post %s already exists.' ), stripslashes( $post_title ) ); - } else { - printf( __( 'Imported post %s...' ), stripslashes( $post_title ) ); - $postdata = compact( 'post_author', 'post_date', 'post_content', 'post_title', 'post_status', 'post_password', 'tags_input', 'comment_status' ); - $post_id = wp_insert_post( $postdata, true ); - if ( is_wp_error( $post_id ) ) { - if ( 'empty_content' == $post_id->get_error_code() ) - return; // Silent skip on "empty" posts - return $post_id; - } - if ( !$post_id ) { - _e( 'Couldn’t get post ID (creating post failed!)' ); - echo '
  • '; - return new WP_Error( 'insert_post_failed', __( 'Failed to create post.' ) ); - } - - // Handle all the metadata for this post - $this->insert_postmeta( $post_id, $post ); - } - echo ''; - } - - // Convert lj-user tags to links to that user - function translate_lj_user( $str ) { - return preg_replace( '||', '$1', $str ); - } - - function insert_postmeta( $post_id, $post ) { - // Need the original LJ id for comments - add_post_meta( $post_id, 'lj_itemid', $post['itemid'] ); - - // And save the permalink on LJ in case we want to link back or something - add_post_meta( $post_id, 'lj_permalink', $post['url'] ); - - // Supports the following "props" from LJ, saved as lj_ in wp_postmeta - // Adult Content - adult_content - // Location - current_coords + current_location - // Mood - current_mood (translated from current_moodid) - // Music - current_music - // Userpic - picture_keyword - foreach ( array( 'adult_content', 'current_coords', 'current_location', 'current_moodid', 'current_music', 'picture_keyword' ) as $prop ) { - if ( !empty( $post['props'][$prop] ) ) { - if ( 'current_moodid' == $prop ) { - $prop = 'current_mood'; - $val = $this->moods[ $post['props']['current_moodid'] ]; - } else { - $val = $post['props'][$prop]; - } - add_post_meta( $post_id, 'lj_' . $prop, $val ); - } - } - } - - // Set up a session (authenticate) with LJ - function get_session() { - // Get a session via XMLRPC - $cookie = $this->lj_ixr( 'sessiongenerate', array( 'ver' => 1, 'expiration' => 'short' ) ); - if ( is_wp_error( $cookie ) ) - return new WP_Error( 'cookie', __( 'Could not get a cookie from LiveJournal. Please try again soon.' ) ); - return new WP_Http_Cookie( array( 'name' => 'ljsession', 'value' => $cookie['ljsession'] ) ); - } - - // Loops through and gets comment meta from LJ in batches - function download_comment_meta() { - $cookie = $this->get_session(); - if ( is_wp_error( $cookie ) ) - return $cookie; - - // Load previous state (if any) - $this->usermap = (array) get_option( 'ljapi_usermap' ); - $maxid = get_option( 'ljapi_maxid' ) ? get_option( 'ljapi_maxid' ) : 1; - $highest_id = get_option( 'ljapi_highest_id' ) ? get_option( 'ljapi_highest_id' ) : 0; - - // We need to loop over the metadata request until we have it all - while ( $maxid > $highest_id ) { - // Now get the meta listing - $results = wp_remote_get( $this->comments_url . '?get=comment_meta&startid=' . ( $highest_id + 1 ), - array( 'cookies' => array( $cookie ), 'timeout' => 20 ) ); - if ( is_wp_error( $results ) ) - return new WP_Error( 'comment_meta', __( 'Failed to retrieve comment meta information from LiveJournal. Please try again soon.' ) ); - - $results = wp_remote_retrieve_body( $results ); - - // Get the maxid so we know if we have them all yet - preg_match( '|(\d+)|', $results, $matches ); - if ( 0 == $matches[1] ) { - // No comment meta = no comments for this journal - echo '

    ' . __( 'You have no comments to import!' ) . '

    '; - update_option( 'ljapi_highest_id', 1 ); - update_option( 'ljapi_highest_comment_id', 1 ); - return false; // Bail out of comment importing entirely - } - $maxid = !empty( $matches[1] ) ? $matches[1] : $maxid; - - // Parse comments and get highest id available - preg_match_all( '| $highest_id ) - $highest_id = $id; - } - - // Parse out the list of user mappings, and add it to the known list - preg_match_all( '||', $results, $matches ); - foreach ( $matches[1] as $count => $userid ) - $this->usermap[$userid] = $matches[2][$count]; // need this in memory for translating ids => names - - wp_cache_flush(); - } - // endwhile - should have seen all comment meta at this point - - update_option( 'ljapi_usermap', $this->usermap ); - update_option( 'ljapi_maxid', $maxid ); - update_option( 'ljapi_highest_id', $highest_id ); - - echo '

    ' . __( ' Comment metadata downloaded successfully, proceeding with comment bodies...' ) . '

    '; - - return true; - } - - // Downloads actual comment bodies from LJ - // Inserts them all directly to the DB, with additional info stored in "spare" fields - function download_comment_bodies() { - global $wpdb; - $cookie = $this->get_session(); - if ( is_wp_error( $cookie ) ) - return $cookie; - - // Load previous state (if any) - $this->usermap = (array) get_option( 'ljapi_usermap' ); - $maxid = get_option( 'ljapi_maxid' ) ? (int) get_option( 'ljapi_maxid' ) : 1; - $highest_id = (int) get_option( 'ljapi_highest_comment_id' ); - $loop = 0; - while ( $maxid > $highest_id && $loop < 5 ) { // We do 5 loops per call to avoid memory limits - $loop++; - - // Get a batch of comments, using the highest_id we've already got as a starting point - $results = wp_remote_get( $this->comments_url . '?get=comment_body&startid=' . ( $highest_id + 1 ), - array( 'cookies' => array( $cookie ), 'timeout' => 20 ) ); - if ( is_wp_error( $results ) ) - return new WP_Error( 'comment_bodies', __( 'Failed to retrieve comment bodies from LiveJournal. Please try again soon.' ) ); - - $results = wp_remote_retrieve_body( $results ); - - // Parse out each comment and insert directly - preg_match_all( '||iUs', $results, $matches ); - for ( $c = 0; $c < count( $matches[0] ); $c++ ) { - // Keep track of highest id seen - if ( $matches[1][$c] > $highest_id ) { - $highest_id = $matches[1][$c]; - update_option( 'ljapi_highest_comment_id', $highest_id ); - } - - $comment = $matches[0][$c]; - - // Filter out any captured, deleted comments (nothing useful to import) - $comment = preg_replace( '||is', '', $comment ); - - // Parse this comment into an array and insert - $comment = $this->parse_comment( $comment ); - $id = wp_insert_comment( $comment ); - - // Clear cache - clean_comment_cache( $id ); - } - - // Clear cache to preseve memory - wp_cache_flush(); - } - // endwhile - all comments downloaded and ready for bulk processing - - // Counter just used to show progress to user - update_option( 'ljapi_comment_batch', ( (int) get_option( 'ljapi_comment_batch' ) + 1 ) ); - - return true; - } - - // Takes a block of XML and parses out all the elements of the comment - function parse_comment( $comment ) { - global $wpdb; - - // Get the top-level attributes - preg_match( '|]+)>|i', $comment, $attribs ); - preg_match( '| id=\'(\d+)\'|i', $attribs[1], $matches ); - $lj_comment_ID = $matches[1]; - preg_match( '| jitemid=\'(\d+)\'|i', $attribs[1], $matches ); - $lj_comment_post_ID = $matches[1]; - preg_match( '| posterid=\'(\d+)\'|i', $attribs[1], $matches ); - $comment_author_ID = isset( $matches[1] ) ? $matches[1] : 0; - preg_match( '| parentid=\'(\d+)\'|i', $attribs[1], $matches ); // optional - $lj_comment_parent = isset( $matches[1] ) ? $matches[1] : 0; - preg_match( '| state=\'([SDFA])\'|i', $attribs[1], $matches ); // optional - $lj_comment_state = isset( $matches[1] ) ? $matches[1] : 'A'; - - // Clean up "subject" - this will become the first line of the comment in WP - preg_match( '|(.*)|is', $comment, $matches ); - if ( isset( $matches[1] ) ) { - $comment_subject = $wpdb->escape( trim( $matches[1] ) ); - if ( 'Re:' == $comment_subject ) - $comment_subject = ''; - } - - // Get the body and HTMLize it - preg_match( '|(.*)|is', $comment, $matches ); - $comment_content = !empty( $comment_subject ) ? $comment_subject . "\n\n" . $matches[1] : $matches[1]; - $comment_content = @html_entity_decode( $comment_content, ENT_COMPAT, get_option('blog_charset') ); - $comment_content = str_replace( ''', "'", $comment_content ); - $comment_content = wpautop( $comment_content ); - $comment_content = str_replace( '
    ', '
    ', $comment_content ); - $comment_content = str_replace( '
    ', '
    ', $comment_content ); - $comment_content = preg_replace_callback( '|<(/?[A-Z]+)|', create_function( '$match', 'return "<" . strtolower( $match[1] );' ), $comment_content ); - $comment_content = $wpdb->escape( trim( $comment_content ) ); - - // Get and convert the date - preg_match( '|(.*)|i', $comment, $matches ); - $comment_date = trim( str_replace( array( 'T', 'Z' ), ' ', $matches[1] ) ); - - // Grab IP if available - preg_match( '|(.*)|i', $comment, $matches ); // optional - $comment_author_IP = isset( $matches[1] ) ? $matches[1] : ''; - - // Try to get something useful for the comment author, especially if it was "my" comment - $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]; - if ( get_option( 'ljapi_username' ) == $author ) { - $user = wp_get_current_user(); - $user_id = $user->ID; - $author = $user->display_name; - $url = trailingslashit( get_option( 'home' ) ); - } else { - $user_id = 0; - $url = ( __( 'Anonymous' ) == $author ) ? '' : 'http://' . $author . '.livejournal.com/'; - } - - // Send back the array of details - return array( 'lj_comment_ID' => $lj_comment_ID, - 'lj_comment_post_ID' => $lj_comment_post_ID, - 'lj_comment_parent' => ( !empty( $lj_comment_parent ) ? $lj_comment_parent : 0 ), - 'lj_comment_state' => $lj_comment_state, - 'comment_post_ID' => $this->get_wp_post_ID( $lj_comment_post_ID ), - 'comment_author' => $author, - 'comment_author_url' => $url, - 'comment_author_email' => '', - 'comment_content' => $comment_content, - 'comment_date' => $comment_date, - 'comment_author_IP' => ( !empty( $comment_author_IP ) ? $comment_author_IP : '' ), - 'comment_approved' => ( in_array( $lj_comment_state, array( 'A', 'F' ) ) ? 1 : 0 ), - 'comment_karma' => $lj_comment_ID, // Need this and next value until rethreading is done - 'comment_agent' => $lj_comment_parent, - 'comment_type' => 'livejournal', // Custom type, so we can find it later for processing - 'user_ID' => $user_id - ); - } - - - // Gets the post_ID that a LJ post has been saved as within WP - function get_wp_post_ID( $post ) { - global $wpdb; - - if ( empty( $this->postmap[$post] ) ) - $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 ) ); - - return $this->postmap[$post]; - } - - // Gets the comment_ID that a LJ comment has been saved as within WP - function get_wp_comment_ID( $comment ) { - global $wpdb; - if ( empty( $this->commentmap[$comment] ) ) - $this->commentmap[$comment] = $wpdb->get_var( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_karma = %d", $comment ) ); - return $this->commentmap[$comment]; - } - - function lj_ixr() { - if ( $challenge = $this->ixr->query( 'LJ.XMLRPC.getchallenge' ) ) { - $challenge = $this->ixr->getResponse(); - } - if ( isset( $challenge['challenge'] ) ) { - $params = array( 'username' => $this->username, - 'auth_method' => 'challenge', - 'auth_challenge' => $challenge['challenge'], - 'auth_response' => md5( $challenge['challenge'] . md5( $this->password ) ) ); - } else { - return new WP_Error( 'IXR', __( 'LiveJournal is not responding to authentication requests. Please wait a while and then try again.' ) ); - } - - $args = func_get_args(); - $method = array_shift( $args ); - if ( isset( $args[0] ) ) - $params = array_merge( $params, $args[0] ); - if ( $this->ixr->query( 'LJ.XMLRPC.' . $method, $params ) ) { - return $this->ixr->getResponse(); - } else { - return new WP_Error( 'IXR', __( 'XML-RPC Request Failed -- ' ) . $this->ixr->getErrorCode() . ': ' . $this->ixr->getErrorMessage() ); - } - } - - function dispatch() { - if ( empty( $_REQUEST['step'] ) ) - $step = 0; - else - $step = (int) $_REQUEST['step']; - - $this->header(); - - switch ( $step ) { - case -1 : - $this->cleanup(); - // Intentional no break - case 0 : - $this->greet(); - break; - case 1 : - case 2 : - case 3 : - check_admin_referer( 'lj-api-import' ); - $result = $this->{ 'step' . $step }(); - if ( is_wp_error( $result ) ) { - $this->throw_error( $result, $step ); - } - break; - } - - $this->footer(); - } - - // Technically the first half of step 1, this is separated to allow for AJAX - // calls. Sets up some variables and options and confirms authentication. - function setup() { - global $verified; - // Get details from form or from DB - if ( !empty( $_POST['lj_username'] ) && !empty( $_POST['lj_password'] ) ) { - // Store details for later - $this->username = $_POST['lj_username']; - $this->password = $_POST['lj_password']; - update_option( 'ljapi_username', $this->username ); - update_option( 'ljapi_password', $this->password ); - } else { - $this->username = get_option( 'ljapi_username' ); - $this->password = get_option( 'ljapi_password' ); - } - - // This is the password to set on protected posts - if ( !empty( $_POST['protected_password'] ) ) { - $this->protected_password = $_POST['protected_password']; - update_option( 'ljapi_protected_password', $this->protected_password ); - } else { - $this->protected_password = get_option( 'ljapi_protected_password' ); - } - - // Login to confirm the details are correct - if ( empty( $this->username ) || empty( $this->password ) ) { - ?> -

    and password so we can download your posts and comments.' ) ?>

    -

    - lj_ixr( 'login' ); - if ( is_wp_error( $verified ) ) { - if ( 100 == $this->ixr->getErrorCode() || 101 == $this->ixr->getErrorCode() ) { - delete_option( 'ljapi_username' ); - delete_option( 'ljapi_password' ); - delete_option( 'ljapi_protected_password' ); - ?> -

    -

    - ixr ) $this->ixr = new IXR_Client( $this->ixr_url, false, 80, 30 ); - if ( empty( $_POST['login'] ) ) { - // We're looping -- load some details from DB - $this->username = get_option( 'ljapi_username' ); - $this->password = get_option( 'ljapi_password' ); - $this->protected_password = get_option( 'ljapi_protected_password' ); - } else { - // First run (non-AJAX) - $setup = $this->setup(); - if ( !$setup ) { - return false; - } else if ( is_wp_error( $setup ) ) { - $this->throw_error( $setup, 1 ); - return false; - } - } - - echo '
    '; - echo '

    ' . __( 'Importing Posts' ) . '

    '; - echo '

    ' . __( 'We’re downloading and importing your LiveJournal posts...' ) . '

    '; - if ( get_option( 'ljapi_post_batch' ) && count( get_option( 'ljapi_sync_item_times' ) ) ) { - $batch = count( get_option( 'ljapi_sync_item_times' ) ); - $batch = $count > 300 ? ceil( $batch / 300 ) : 1; - echo '

    ' . sprintf( __( 'Imported post batch %d of approximately %d' ), ( get_option( 'ljapi_post_batch' ) + 1 ), $batch ) . '

    '; - } - ob_flush(); flush(); - - if ( !get_option( 'ljapi_lastsync' ) || '1900-01-01 00:00:00' == get_option( 'ljapi_lastsync' ) ) { - // We haven't downloaded meta yet, so do that first - $result = $this->download_post_meta(); - if ( is_wp_error( $result ) ) { - $this->throw_error( $result, 1 ); - return false; - } - } - - // Download a batch of actual posts - $result = $this->download_post_bodies(); - if ( is_wp_error( $result ) ) { - if ( 406 == $this->ixr->getErrorCode() ) { - ?> -

    -

    - next_step( 1, __( 'Try Again' ) ); - return false; - } else { - $this->throw_error( $result, 1 ); - return false; - } - } - - if ( get_option( 'ljapi_last_sync_count' ) > 0 ) { - ?> -
    - - -

    -
    - auto_ajax( 'ljapi-auto-repost', 'auto-message', 0 ); ?> - ' . __( 'Your posts have all been imported, but wait – there’s more! Now we need to download & import your comments.' ) . '

    '; - echo $this->next_step( 2, __( 'Download my comments »' ) ); - $this->auto_submit(); - } - echo '
    '; - } - - // Download comments to local XML - function step2() { - set_time_limit( 0 ); - update_option( 'ljapi_step', 2 ); - $this->username = get_option( 'ljapi_username' ); - $this->password = get_option( 'ljapi_password' ); - $this->ixr = new IXR_Client( $this->ixr_url, false, 80, 30 ); - - echo '
    '; - echo '

    ' . __( 'Downloading Comments' ) . '

    '; - echo '

    ' . __( 'Now we will download your comments so we can import them (this could take a long time if you have lots of comments)...' ) . '

    '; - ob_flush(); flush(); - - if ( !get_option( 'ljapi_usermap' ) ) { - // We haven't downloaded meta yet, so do that first - $result = $this->download_comment_meta(); - if ( is_wp_error( $result ) ) { - $this->throw_error( $result, 2 ); - return false; - } - } - - // Download a batch of actual comments - $result = $this->download_comment_bodies(); - if ( is_wp_error( $result ) ) { - $this->throw_error( $result, 2 ); - return false; - } - - $maxid = get_option( 'ljapi_maxid' ) ? (int) get_option( 'ljapi_maxid' ) : 1; - $highest_id = (int) get_option( 'ljapi_highest_comment_id' ); - if ( $maxid > $highest_id ) { - $batch = $maxid > 5000 ? ceil( $maxid / 5000 ) : 1; - ?> -
    -

    approximately %d' ), get_option( 'ljapi_comment_batch' ), $batch ) ?>

    - - -

    -
    - auto_ajax( 'ljapi-auto-repost', 'auto-message', 0 ); ?> - ' . __( 'Your comments have all been imported now, but we still need to rebuild your conversation threads.' ) . '

    '; - echo $this->next_step( 3, __( 'Rebuild my comment threads »' ) ); - $this->auto_submit(); - } - echo '
    '; - } - - // Re-thread comments already in the DB - function step3() { - global $wpdb; - set_time_limit( 0 ); - update_option( 'ljapi_step', 3 ); - - echo '
    '; - echo '

    ' . __( 'Threading Comments' ) . '

    '; - echo '

    ' . __( 'We are now re-building the threading of your comments (this can also take a while if you have lots of comments)...' ) . '

    '; - ob_flush(); flush(); - - // Only bother adding indexes if they have over 5000 comments (arbitrary number) - $imported_comments = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_type = 'livejournal'" ); - $added_indices = false; - if ( 5000 < $imported_comments ) { - include_once(ABSPATH . 'wp-admin/includes/upgrade.php'); - $added_indices = true; - add_clean_index( $wpdb->comments, 'comment_type' ); - add_clean_index( $wpdb->comments, 'comment_karma' ); - add_clean_index( $wpdb->comments, 'comment_agent' ); - } - - // Get LJ comments, which haven't been threaded yet, 5000 at a time and thread them - while ( $comments = $wpdb->get_results( "SELECT comment_ID, comment_agent FROM {$wpdb->comments} WHERE comment_type = 'livejournal' AND comment_agent != '0' LIMIT 5000", OBJECT ) ) { - foreach ( $comments as $comment ) { - $wpdb->update( $wpdb->comments, - array( 'comment_parent' => $this->get_wp_comment_ID( $comment->comment_agent ), 'comment_type' => 'livejournal-done' ), - array( 'comment_ID' => $comment->comment_ID ) ); - } - wp_cache_flush(); - $wpdb->flush(); - } - - // Revert the comments table back to normal and optimize it to reclaim space - if ( $added_indices ) { - drop_index( $wpdb->comments, 'comment_type' ); - drop_index( $wpdb->comments, 'comment_karma' ); - drop_index( $wpdb->comments, 'comment_agent' ); - $wpdb->query( "OPTIMIZE TABLE {$wpdb->comments}" ); - } - - // Clean up database and we're out - $this->cleanup(); - do_action( 'import_done', 'livejournal' ); - if ( $imported_comments > 1 ) - echo '

    ' . sprintf( __( "Successfully re-threaded %s comments." ), number_format( $imported_comments ) ) . '

    '; - echo '

    '; - printf( __( 'All done. Have fun!' ), get_option( 'home' ) ); - echo '

    '; - echo '
    '; - } - - // Output an error message with a button to try again. - function throw_error( $error, $step ) { - echo '

    ' . $error->get_error_message() . '

    '; - echo $this->next_step( $step, __( 'Try Again' ) ); - } - - // Returns the HTML for a link to the next page - function next_step( $next_step, $label, $id = 'ljapi-next-form' ) { - $str = '
    '; - $str .= wp_nonce_field( 'lj-api-import', '_wpnonce', true, false ); - $str .= wp_referer_field( false ); - $str .= ''; - $str .= '

    '; - $str .= '
    '; - - return $str; - } - - // Automatically submit the specified form after $seconds - // Include a friendly countdown in the element with id=$msg - function auto_submit( $id = 'ljapi-next-form', $msg = 'auto-message', $seconds = 10 ) { - ?>update( $wpdb->comments, - array( 'comment_karma' => 0, 'comment_agent' => 'WP LJ Importer', 'comment_type' => '' ), - array( 'comment_type' => 'livejournal-done' ) ); - $wpdb->update( $wpdb->comments, - array( 'comment_karma' => 0, 'comment_agent' => 'WP LJ Importer', 'comment_type' => '' ), - array( 'comment_type' => 'livejournal' ) ); - } - - function LJ_API_Import() { - $this->__construct(); - } - - function __construct() { - // Nothing - } -} - -$lj_api_import = new LJ_API_Import(); - -register_importer( 'livejournal', __( 'LiveJournal' ), __( 'Import posts from LiveJournal using their API.' ), array( $lj_api_import, 'dispatch' ) ); -?> + 'aggravated', + '10' => 'discontent', + '100' => 'rushed', + '101' => 'contemplative', + '102' => 'nerdy', + '103' => 'geeky', + '104' => 'cynical', + '105' => 'quixotic', + '106' => 'crazy', + '107' => 'creative', + '108' => 'artistic', + '109' => 'pleased', + '11' => 'energetic', + '110' => 'bitchy', + '111' => 'guilty', + '112' => 'irritated', + '113' => 'blank', + '114' => 'apathetic', + '115' => 'dorky', + '116' => 'impressed', + '117' => 'naughty', + '118' => 'predatory', + '119' => 'dirty', + '12' => 'enraged', + '120' => 'giddy', + '121' => 'surprised', + '122' => 'shocked', + '123' => 'rejected', + '124' => 'numb', + '125' => 'cheerful', + '126' => 'good', + '127' => 'distressed', + '128' => 'intimidated', + '129' => 'crushed', + '13' => 'enthralled', + '130' => 'devious', + '131' => 'thankful', + '132' => 'grateful', + '133' => 'jealous', + '134' => 'nervous', + '14' => 'exhausted', + '15' => 'happy', + '16' => 'high', + '17' => 'horny', + '18' => 'hungry', + '19' => 'infuriated', + '2' => 'angry', + '20' => 'irate', + '21' => 'jubilant', + '22' => 'lonely', + '23' => 'moody', + '24' => 'pissed off', + '25' => 'sad', + '26' => 'satisfied', + '27' => 'sore', + '28' => 'stressed', + '29' => 'thirsty', + '3' => 'annoyed', + '30' => 'thoughtful', + '31' => 'tired', + '32' => 'touched', + '33' => 'lazy', + '34' => 'drunk', + '35' => 'ditzy', + '36' => 'mischievous', + '37' => 'morose', + '38' => 'gloomy', + '39' => 'melancholy', + '4' => 'anxious', + '40' => 'drained', + '41' => 'excited', + '42' => 'relieved', + '43' => 'hopeful', + '44' => 'amused', + '45' => 'determined', + '46' => 'scared', + '47' => 'frustrated', + '48' => 'indescribable', + '49' => 'sleepy', + '5' => 'bored', + '51' => 'groggy', + '52' => 'hyper', + '53' => 'relaxed', + '54' => 'restless', + '55' => 'disappointed', + '56' => 'curious', + '57' => 'mellow', + '58' => 'peaceful', + '59' => 'bouncy', + '6' => 'confused', + '60' => 'nostalgic', + '61' => 'okay', + '62' => 'rejuvenated', + '63' => 'complacent', + '64' => 'content', + '65' => 'indifferent', + '66' => 'silly', + '67' => 'flirty', + '68' => 'calm', + '69' => 'refreshed', + '7' => 'crappy', + '70' => 'optimistic', + '71' => 'pessimistic', + '72' => 'giggly', + '73' => 'pensive', + '74' => 'uncomfortable', + '75' => 'lethargic', + '76' => 'listless', + '77' => 'recumbent', + '78' => 'exanimate', + '79' => 'embarrassed', + '8' => 'cranky', + '80' => 'envious', + '81' => 'sympathetic', + '82' => 'sick', + '83' => 'hot', + '84' => 'cold', + '85' => 'worried', + '86' => 'loved', + '87' => 'awake', + '88' => 'working', + '89' => 'productive', + '9' => 'depressed', + '90' => 'accomplished', + '91' => 'busy', + '92' => 'blah', + '93' => 'full', + '95' => 'grumpy', + '96' => 'weird', + '97' => 'nauseated', + '98' => 'ecstatic', + '99' => 'chipper' ); + + function header() { + echo '
    '; + screen_icon(); + echo '

    ' . __( 'Import LiveJournal' ) . '

    '; + } + + function footer() { + echo '
    '; + } + + function greet() { + ?> +
    +
    + + + +

    +

    + +

    +

    +

    + + + +

    +

    + + + + + + + + + + + + + +
    + +

    +

    +

    + + + + + + + +
    + +

    WARNING: 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." ) ?>

    + +

    + +

    + +

    NOTE: If the import process is interrupted for any reason, come back to this page and it will continue from where it stopped automatically.' ) ?>

    + + + +
    +
    + lj_ixr( 'syncitems', array( 'ver' => 1, 'lastsync' => $lastsync ) ); + if ( is_wp_error( $synclist ) ) + return $synclist; + + // Keep track of if we've downloaded everything + $total = $synclist['total']; + $count = $synclist['count']; + + foreach ( $synclist['syncitems'] as $event ) { + if ( substr( $event['item'], 0, 2 ) == 'L-' ) { + $sync_item_times[ str_replace( 'L-', '', $event['item'] ) ] = $event['time']; + if ( $event['time'] > $lastsync ) { + $lastsync = $event['time']; + update_option( 'ljapi_lastsync', $lastsync ); + } + } + } + } while ( $total > $count ); + // endwhile - all post meta is cached locally + unset( $synclist ); + update_option( 'ljapi_sync_item_times', $sync_item_times ); + update_option( 'ljapi_total', $total ); + update_option( 'ljapi_count', $count ); + + echo '

    ' . __( 'Post metadata has been downloaded, proceeding with posts...' ) . '

    '; + } + + function download_post_bodies() { + $imported_count = (int) get_option( 'ljapi_imported_count' ); + $sync_item_times = get_option( 'ljapi_sync_item_times' ); + $lastsync = get_option( 'ljapi_lastsync_posts' ); + if ( !$lastsync ) + update_option( 'ljapi_lastsync_posts', date( 'Y-m-d H:i:s', 0 ) ); + + $count = 0; + echo '
      '; + do { + $lastsync = date( 'Y-m-d H:i:s', strtotime( get_option( 'ljapi_lastsync_posts' ) ) ); + + // Get the batch of items that match up with the syncitems list + $itemlist = $this->lj_ixr( 'getevents', array( 'ver' => 1, + 'selecttype' => 'syncitems', + 'lineendings' => 'pc', + 'lastsync' => $lastsync ) ); + if ( is_wp_error( $itemlist ) ) + return $itemlist; + + if ( $num = count( $itemlist['events'] ) ) { + for ( $e = 0; $e < count( $itemlist['events'] ); $e++ ) { + $event = $itemlist['events'][$e]; + $imported_count++; + $inserted = $this->import_post( $event ); + if ( is_wp_error( $inserted ) ) + return $inserted; + if ( $sync_item_times[ $event['itemid'] ] > $lastsync ) + $lastsync = $sync_item_times[ $event['itemid'] ]; + wp_cache_flush(); + } + update_option( 'ljapi_lastsync_posts', $lastsync ); + update_option( 'ljapi_imported_count', $imported_count ); + update_option( 'ljapi_last_sync_count', $num ); + } + $count++; + } while ( $num > 0 && $count < 3 ); // Doing up to 3 requests at a time to avoid memory problems + + // Used so that step1 knows when to stop posting back on itself + update_option( 'ljapi_last_sync_count', $num ); + + // Counter just used to show progress to user + update_option( 'ljapi_post_batch', ( (int) get_option( 'ljapi_post_batch' ) + 1 ) ); + + echo '
    '; + } + + function _normalize_tag( $matches ) { + return '<' . strtolower( $matches[1] ); + } + + function import_post( $post ) { + global $wpdb; + + // Make sure we haven't already imported this one + if ( $this->get_wp_post_ID( $post['itemid'] ) ) + return; + + $user = wp_get_current_user(); + $post_author = $user->ID; + $post['security'] = !empty( $post['security'] ) ? $post['security'] : ''; + $post_status = ( 'private' == trim( $post['security'] ) ) ? 'private' : 'publish'; // Only me + $post_password = ( 'usemask' == trim( $post['security'] ) ) ? $this->protected_password : ''; // "Friends" via password + + // For some reason, LJ sometimes sends a date as "2004-04-1408:38:00" (no space btwn date/time) + $post_date = $post['eventtime']; + if ( 18 == strlen( $post_date ) ) + $post_date = substr( $post_date, 0, 10 ) . ' ' . substr( $post_date, 10 ); + + // Cleaning up and linking the title + $post_title = isset( $post['subject'] ) ? trim( $post['subject'] ) : ''; + $post_title = $this->translate_lj_user( $post_title ); // Translate it, but then we'll strip the link + $post_title = strip_tags( $post_title ); // Can't have tags in the title in WP + $post_title = $wpdb->escape( $post_title ); + + // Clean up content + $post_content = $post['event']; + $post_content = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_content ); + // XHTMLize some tags + $post_content = str_replace( '
    ', '
    ', $post_content ); + $post_content = str_replace( '
    ', '
    ', $post_content ); + // lj-cut ==> + $post_content = preg_replace( '||is', '', $post_content ); + $post_content = str_replace( array( '', '' ), array( '', '' ), $post_content ); + $first = strpos( $post_content, '|sUi', '', substr( $post_content, $first + 1 ) ); + // lj-user ==> a href + $post_content = $this->translate_lj_user( $post_content ); + //$post_content = force_balance_tags( $post_content ); + $post_content = $wpdb->escape( $post_content ); + + // Handle any tags associated with the post + $tags_input = !empty( $post['props']['taglist'] ) ? $post['props']['taglist'] : ''; + + // Check if comments are closed on this post + $comment_status = !empty( $post['props']['opt_nocomments'] ) ? 'closed' : 'open'; + + echo '
  • '; + if ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) { + printf( __( 'Post %s already exists.' ), stripslashes( $post_title ) ); + } else { + printf( __( 'Imported post %s...' ), stripslashes( $post_title ) ); + $postdata = compact( 'post_author', 'post_date', 'post_content', 'post_title', 'post_status', 'post_password', 'tags_input', 'comment_status' ); + $post_id = wp_insert_post( $postdata, true ); + if ( is_wp_error( $post_id ) ) { + if ( 'empty_content' == $post_id->get_error_code() ) + return; // Silent skip on "empty" posts + return $post_id; + } + if ( !$post_id ) { + _e( 'Couldn’t get post ID (creating post failed!)' ); + echo '
  • '; + return new WP_Error( 'insert_post_failed', __( 'Failed to create post.' ) ); + } + + // Handle all the metadata for this post + $this->insert_postmeta( $post_id, $post ); + } + echo ''; + } + + // Convert lj-user tags to links to that user + function translate_lj_user( $str ) { + return preg_replace( '||', '$1', $str ); + } + + function insert_postmeta( $post_id, $post ) { + // Need the original LJ id for comments + add_post_meta( $post_id, 'lj_itemid', $post['itemid'] ); + + // And save the permalink on LJ in case we want to link back or something + add_post_meta( $post_id, 'lj_permalink', $post['url'] ); + + // Supports the following "props" from LJ, saved as lj_ in wp_postmeta + // Adult Content - adult_content + // Location - current_coords + current_location + // Mood - current_mood (translated from current_moodid) + // Music - current_music + // Userpic - picture_keyword + foreach ( array( 'adult_content', 'current_coords', 'current_location', 'current_moodid', 'current_music', 'picture_keyword' ) as $prop ) { + if ( !empty( $post['props'][$prop] ) ) { + if ( 'current_moodid' == $prop ) { + $prop = 'current_mood'; + $val = $this->moods[ $post['props']['current_moodid'] ]; + } else { + $val = $post['props'][$prop]; + } + add_post_meta( $post_id, 'lj_' . $prop, $val ); + } + } + } + + // Set up a session (authenticate) with LJ + function get_session() { + // Get a session via XMLRPC + $cookie = $this->lj_ixr( 'sessiongenerate', array( 'ver' => 1, 'expiration' => 'short' ) ); + if ( is_wp_error( $cookie ) ) + return new WP_Error( 'cookie', __( 'Could not get a cookie from LiveJournal. Please try again soon.' ) ); + return new WP_Http_Cookie( array( 'name' => 'ljsession', 'value' => $cookie['ljsession'] ) ); + } + + // Loops through and gets comment meta from LJ in batches + function download_comment_meta() { + $cookie = $this->get_session(); + if ( is_wp_error( $cookie ) ) + return $cookie; + + // Load previous state (if any) + $this->usermap = (array) get_option( 'ljapi_usermap' ); + $maxid = get_option( 'ljapi_maxid' ) ? get_option( 'ljapi_maxid' ) : 1; + $highest_id = get_option( 'ljapi_highest_id' ) ? get_option( 'ljapi_highest_id' ) : 0; + + // We need to loop over the metadata request until we have it all + while ( $maxid > $highest_id ) { + // Now get the meta listing + $results = wp_remote_get( $this->comments_url . '?get=comment_meta&startid=' . ( $highest_id + 1 ), + array( 'cookies' => array( $cookie ), 'timeout' => 20 ) ); + if ( is_wp_error( $results ) ) + return new WP_Error( 'comment_meta', __( 'Failed to retrieve comment meta information from LiveJournal. Please try again soon.' ) ); + + $results = wp_remote_retrieve_body( $results ); + + // Get the maxid so we know if we have them all yet + preg_match( '|(\d+)|', $results, $matches ); + if ( 0 == $matches[1] ) { + // No comment meta = no comments for this journal + echo '

    ' . __( 'You have no comments to import!' ) . '

    '; + update_option( 'ljapi_highest_id', 1 ); + update_option( 'ljapi_highest_comment_id', 1 ); + return false; // Bail out of comment importing entirely + } + $maxid = !empty( $matches[1] ) ? $matches[1] : $maxid; + + // Parse comments and get highest id available + preg_match_all( '| $highest_id ) + $highest_id = $id; + } + + // Parse out the list of user mappings, and add it to the known list + preg_match_all( '||', $results, $matches ); + foreach ( $matches[1] as $count => $userid ) + $this->usermap[$userid] = $matches[2][$count]; // need this in memory for translating ids => names + + wp_cache_flush(); + } + // endwhile - should have seen all comment meta at this point + + update_option( 'ljapi_usermap', $this->usermap ); + update_option( 'ljapi_maxid', $maxid ); + update_option( 'ljapi_highest_id', $highest_id ); + + echo '

    ' . __( ' Comment metadata downloaded successfully, proceeding with comment bodies...' ) . '

    '; + + return true; + } + + // Downloads actual comment bodies from LJ + // Inserts them all directly to the DB, with additional info stored in "spare" fields + function download_comment_bodies() { + global $wpdb; + $cookie = $this->get_session(); + if ( is_wp_error( $cookie ) ) + return $cookie; + + // Load previous state (if any) + $this->usermap = (array) get_option( 'ljapi_usermap' ); + $maxid = get_option( 'ljapi_maxid' ) ? (int) get_option( 'ljapi_maxid' ) : 1; + $highest_id = (int) get_option( 'ljapi_highest_comment_id' ); + $loop = 0; + while ( $maxid > $highest_id && $loop < 5 ) { // We do 5 loops per call to avoid memory limits + $loop++; + + // Get a batch of comments, using the highest_id we've already got as a starting point + $results = wp_remote_get( $this->comments_url . '?get=comment_body&startid=' . ( $highest_id + 1 ), + array( 'cookies' => array( $cookie ), 'timeout' => 20 ) ); + if ( is_wp_error( $results ) ) + return new WP_Error( 'comment_bodies', __( 'Failed to retrieve comment bodies from LiveJournal. Please try again soon.' ) ); + + $results = wp_remote_retrieve_body( $results ); + + // Parse out each comment and insert directly + preg_match_all( '||iUs', $results, $matches ); + for ( $c = 0; $c < count( $matches[0] ); $c++ ) { + // Keep track of highest id seen + if ( $matches[1][$c] > $highest_id ) { + $highest_id = $matches[1][$c]; + update_option( 'ljapi_highest_comment_id', $highest_id ); + } + + $comment = $matches[0][$c]; + + // Filter out any captured, deleted comments (nothing useful to import) + $comment = preg_replace( '||is', '', $comment ); + + // Parse this comment into an array and insert + $comment = $this->parse_comment( $comment ); + $comment = wp_filter_comment( $comment ); + $id = wp_insert_comment( $comment ); + + // Clear cache + clean_comment_cache( $id ); + } + + // Clear cache to preseve memory + wp_cache_flush(); + } + // endwhile - all comments downloaded and ready for bulk processing + + // Counter just used to show progress to user + update_option( 'ljapi_comment_batch', ( (int) get_option( 'ljapi_comment_batch' ) + 1 ) ); + + return true; + } + + // Takes a block of XML and parses out all the elements of the comment + function parse_comment( $comment ) { + global $wpdb; + + // Get the top-level attributes + preg_match( '|]+)>|i', $comment, $attribs ); + preg_match( '| id=\'(\d+)\'|i', $attribs[1], $matches ); + $lj_comment_ID = $matches[1]; + preg_match( '| jitemid=\'(\d+)\'|i', $attribs[1], $matches ); + $lj_comment_post_ID = $matches[1]; + preg_match( '| posterid=\'(\d+)\'|i', $attribs[1], $matches ); + $comment_author_ID = isset( $matches[1] ) ? $matches[1] : 0; + preg_match( '| parentid=\'(\d+)\'|i', $attribs[1], $matches ); // optional + $lj_comment_parent = isset( $matches[1] ) ? $matches[1] : 0; + preg_match( '| state=\'([SDFA])\'|i', $attribs[1], $matches ); // optional + $lj_comment_state = isset( $matches[1] ) ? $matches[1] : 'A'; + + // Clean up "subject" - this will become the first line of the comment in WP + preg_match( '|(.*)|is', $comment, $matches ); + if ( isset( $matches[1] ) ) { + $comment_subject = $wpdb->escape( trim( $matches[1] ) ); + if ( 'Re:' == $comment_subject ) + $comment_subject = ''; + } + + // Get the body and HTMLize it + preg_match( '|(.*)|is', $comment, $matches ); + $comment_content = !empty( $comment_subject ) ? $comment_subject . "\n\n" . $matches[1] : $matches[1]; + $comment_content = @html_entity_decode( $comment_content, ENT_COMPAT, get_option('blog_charset') ); + $comment_content = str_replace( ''', "'", $comment_content ); + $comment_content = wpautop( $comment_content ); + $comment_content = str_replace( '
    ', '
    ', $comment_content ); + $comment_content = str_replace( '
    ', '
    ', $comment_content ); + $comment_content = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $comment_content ); + $comment_content = $wpdb->escape( trim( $comment_content ) ); + + // Get and convert the date + preg_match( '|(.*)|i', $comment, $matches ); + $comment_date = trim( str_replace( array( 'T', 'Z' ), ' ', $matches[1] ) ); + + // Grab IP if available + preg_match( '|(.*)|i', $comment, $matches ); // optional + $comment_author_IP = isset( $matches[1] ) ? $matches[1] : ''; + + // Try to get something useful for the comment author, especially if it was "my" comment + $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]; + if ( get_option( 'ljapi_username' ) == $author ) { + $user = wp_get_current_user(); + $user_id = $user->ID; + $author = $user->display_name; + $url = trailingslashit( get_option( 'home' ) ); + } else { + $user_id = 0; + $url = ( __( 'Anonymous' ) == $author ) ? '' : 'http://' . $author . '.livejournal.com/'; + } + + // Send back the array of details + return array( 'lj_comment_ID' => $lj_comment_ID, + 'lj_comment_post_ID' => $lj_comment_post_ID, + 'lj_comment_parent' => ( !empty( $lj_comment_parent ) ? $lj_comment_parent : 0 ), + 'lj_comment_state' => $lj_comment_state, + 'comment_post_ID' => $this->get_wp_post_ID( $lj_comment_post_ID ), + 'comment_author' => $author, + 'comment_author_url' => $url, + 'comment_author_email' => '', + 'comment_content' => $comment_content, + 'comment_date' => $comment_date, + 'comment_author_IP' => ( !empty( $comment_author_IP ) ? $comment_author_IP : '' ), + 'comment_approved' => ( in_array( $lj_comment_state, array( 'A', 'F' ) ) ? 1 : 0 ), + 'comment_karma' => $lj_comment_ID, // Need this and next value until rethreading is done + 'comment_agent' => $lj_comment_parent, + 'comment_type' => 'livejournal', // Custom type, so we can find it later for processing + 'user_ID' => $user_id + ); + } + + + // Gets the post_ID that a LJ post has been saved as within WP + function get_wp_post_ID( $post ) { + global $wpdb; + + if ( empty( $this->postmap[$post] ) ) + $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 ) ); + + return $this->postmap[$post]; + } + + // Gets the comment_ID that a LJ comment has been saved as within WP + function get_wp_comment_ID( $comment ) { + global $wpdb; + if ( empty( $this->commentmap[$comment] ) ) + $this->commentmap[$comment] = $wpdb->get_var( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_karma = %d", $comment ) ); + return $this->commentmap[$comment]; + } + + function lj_ixr() { + if ( $challenge = $this->ixr->query( 'LJ.XMLRPC.getchallenge' ) ) { + $challenge = $this->ixr->getResponse(); + } + if ( isset( $challenge['challenge'] ) ) { + $params = array( 'username' => $this->username, + 'auth_method' => 'challenge', + 'auth_challenge' => $challenge['challenge'], + 'auth_response' => md5( $challenge['challenge'] . md5( $this->password ) ) ); + } else { + return new WP_Error( 'IXR', __( 'LiveJournal is not responding to authentication requests. Please wait a while and then try again.' ) ); + } + + $args = func_get_args(); + $method = array_shift( $args ); + if ( isset( $args[0] ) ) + $params = array_merge( $params, $args[0] ); + if ( $this->ixr->query( 'LJ.XMLRPC.' . $method, $params ) ) { + return $this->ixr->getResponse(); + } else { + return new WP_Error( 'IXR', __( 'XML-RPC Request Failed -- ' ) . $this->ixr->getErrorCode() . ': ' . $this->ixr->getErrorMessage() ); + } + } + + function dispatch() { + if ( empty( $_REQUEST['step'] ) ) + $step = 0; + else + $step = (int) $_REQUEST['step']; + + $this->header(); + + switch ( $step ) { + case -1 : + $this->cleanup(); + // Intentional no break + case 0 : + $this->greet(); + break; + case 1 : + case 2 : + case 3 : + check_admin_referer( 'lj-api-import' ); + $result = $this->{ 'step' . $step }(); + if ( is_wp_error( $result ) ) { + $this->throw_error( $result, $step ); + } + break; + } + + $this->footer(); + } + + // Technically the first half of step 1, this is separated to allow for AJAX + // calls. Sets up some variables and options and confirms authentication. + function setup() { + global $verified; + // Get details from form or from DB + if ( !empty( $_POST['lj_username'] ) && !empty( $_POST['lj_password'] ) ) { + // Store details for later + $this->username = $_POST['lj_username']; + $this->password = $_POST['lj_password']; + update_option( 'ljapi_username', $this->username ); + update_option( 'ljapi_password', $this->password ); + } else { + $this->username = get_option( 'ljapi_username' ); + $this->password = get_option( 'ljapi_password' ); + } + + // This is the password to set on protected posts + if ( !empty( $_POST['protected_password'] ) ) { + $this->protected_password = $_POST['protected_password']; + update_option( 'ljapi_protected_password', $this->protected_password ); + } else { + $this->protected_password = get_option( 'ljapi_protected_password' ); + } + + // Log in to confirm the details are correct + if ( empty( $this->username ) || empty( $this->password ) ) { + ?> +

    and password so we can download your posts and comments.' ) ?>

    +

    + lj_ixr( 'login' ); + if ( is_wp_error( $verified ) ) { + if ( 100 == $this->ixr->getErrorCode() || 101 == $this->ixr->getErrorCode() ) { + delete_option( 'ljapi_username' ); + delete_option( 'ljapi_password' ); + delete_option( 'ljapi_protected_password' ); + ?> +

    +

    + ixr ) $this->ixr = new IXR_Client( $this->ixr_url, false, 80, 30 ); + if ( empty( $_POST['login'] ) ) { + // We're looping -- load some details from DB + $this->username = get_option( 'ljapi_username' ); + $this->password = get_option( 'ljapi_password' ); + $this->protected_password = get_option( 'ljapi_protected_password' ); + } else { + // First run (non-AJAX) + $setup = $this->setup(); + if ( !$setup ) { + return false; + } else if ( is_wp_error( $setup ) ) { + $this->throw_error( $setup, 1 ); + return false; + } + } + + echo '
    '; + echo '

    ' . __( 'Importing Posts' ) . '

    '; + echo '

    ' . __( 'We’re downloading and importing your LiveJournal posts...' ) . '

    '; + if ( get_option( 'ljapi_post_batch' ) && count( get_option( 'ljapi_sync_item_times' ) ) ) { + $batch = count( get_option( 'ljapi_sync_item_times' ) ); + $batch = $count > 300 ? ceil( $batch / 300 ) : 1; + echo '

    ' . sprintf( __( 'Imported post batch %d of approximately %d' ), ( get_option( 'ljapi_post_batch' ) + 1 ), $batch ) . '

    '; + } + ob_flush(); flush(); + + if ( !get_option( 'ljapi_lastsync' ) || '1900-01-01 00:00:00' == get_option( 'ljapi_lastsync' ) ) { + // We haven't downloaded meta yet, so do that first + $result = $this->download_post_meta(); + if ( is_wp_error( $result ) ) { + $this->throw_error( $result, 1 ); + return false; + } + } + + // Download a batch of actual posts + $result = $this->download_post_bodies(); + if ( is_wp_error( $result ) ) { + if ( 406 == $this->ixr->getErrorCode() ) { + ?> +

    +

    + next_step( 1, __( 'Try Again' ) ); + return false; + } else { + $this->throw_error( $result, 1 ); + return false; + } + } + + if ( get_option( 'ljapi_last_sync_count' ) > 0 ) { + ?> +
    + + +

    +
    + auto_ajax( 'ljapi-auto-repost', 'auto-message', 0 ); ?> + ' . __( 'Your posts have all been imported, but wait – there’s more! Now we need to download & import your comments.' ) . '

    '; + echo $this->next_step( 2, __( 'Download my comments »' ) ); + $this->auto_submit(); + } + echo '
    '; + } + + // Download comments to local XML + function step2() { + set_time_limit( 0 ); + update_option( 'ljapi_step', 2 ); + $this->username = get_option( 'ljapi_username' ); + $this->password = get_option( 'ljapi_password' ); + $this->ixr = new IXR_Client( $this->ixr_url, false, 80, 30 ); + + echo '
    '; + echo '

    ' . __( 'Downloading Comments' ) . '

    '; + echo '

    ' . __( 'Now we will download your comments so we can import them (this could take a long time if you have lots of comments)...' ) . '

    '; + ob_flush(); flush(); + + if ( !get_option( 'ljapi_usermap' ) ) { + // We haven't downloaded meta yet, so do that first + $result = $this->download_comment_meta(); + if ( is_wp_error( $result ) ) { + $this->throw_error( $result, 2 ); + return false; + } + } + + // Download a batch of actual comments + $result = $this->download_comment_bodies(); + if ( is_wp_error( $result ) ) { + $this->throw_error( $result, 2 ); + return false; + } + + $maxid = get_option( 'ljapi_maxid' ) ? (int) get_option( 'ljapi_maxid' ) : 1; + $highest_id = (int) get_option( 'ljapi_highest_comment_id' ); + if ( $maxid > $highest_id ) { + $batch = $maxid > 5000 ? ceil( $maxid / 5000 ) : 1; + ?> +
    +

    approximately %d' ), get_option( 'ljapi_comment_batch' ), $batch ) ?>

    + + +

    +
    + auto_ajax( 'ljapi-auto-repost', 'auto-message', 0 ); ?> + ' . __( 'Your comments have all been imported now, but we still need to rebuild your conversation threads.' ) . '

    '; + echo $this->next_step( 3, __( 'Rebuild my comment threads »' ) ); + $this->auto_submit(); + } + echo '
    '; + } + + // Re-thread comments already in the DB + function step3() { + global $wpdb; + set_time_limit( 0 ); + update_option( 'ljapi_step', 3 ); + + echo '
    '; + echo '

    ' . __( 'Threading Comments' ) . '

    '; + echo '

    ' . __( 'We are now re-building the threading of your comments (this can also take a while if you have lots of comments)...' ) . '

    '; + ob_flush(); flush(); + + // Only bother adding indexes if they have over 5000 comments (arbitrary number) + $imported_comments = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_type = 'livejournal'" ); + $added_indices = false; + if ( 5000 < $imported_comments ) { + include_once(ABSPATH . 'wp-admin/includes/upgrade.php'); + $added_indices = true; + add_clean_index( $wpdb->comments, 'comment_type' ); + add_clean_index( $wpdb->comments, 'comment_karma' ); + add_clean_index( $wpdb->comments, 'comment_agent' ); + } + + // Get LJ comments, which haven't been threaded yet, 5000 at a time and thread them + while ( $comments = $wpdb->get_results( "SELECT comment_ID, comment_agent FROM {$wpdb->comments} WHERE comment_type = 'livejournal' AND comment_agent != '0' LIMIT 5000", OBJECT ) ) { + foreach ( $comments as $comment ) { + $wpdb->update( $wpdb->comments, + array( 'comment_parent' => $this->get_wp_comment_ID( $comment->comment_agent ), 'comment_type' => 'livejournal-done' ), + array( 'comment_ID' => $comment->comment_ID ) ); + } + wp_cache_flush(); + $wpdb->flush(); + } + + // Revert the comments table back to normal and optimize it to reclaim space + if ( $added_indices ) { + drop_index( $wpdb->comments, 'comment_type' ); + drop_index( $wpdb->comments, 'comment_karma' ); + drop_index( $wpdb->comments, 'comment_agent' ); + $wpdb->query( "OPTIMIZE TABLE {$wpdb->comments}" ); + } + + // Clean up database and we're out + $this->cleanup(); + do_action( 'import_done', 'livejournal' ); + if ( $imported_comments > 1 ) + echo '

    ' . sprintf( __( "Successfully re-threaded %s comments." ), number_format( $imported_comments ) ) . '

    '; + echo '

    '; + printf( __( 'All done. Have fun!' ), get_option( 'home' ) ); + echo '

    '; + echo '
    '; + } + + // Output an error message with a button to try again. + function throw_error( $error, $step ) { + echo '

    ' . $error->get_error_message() . '

    '; + echo $this->next_step( $step, __( 'Try Again' ) ); + } + + // Returns the HTML for a link to the next page + function next_step( $next_step, $label, $id = 'ljapi-next-form' ) { + $str = '
    '; + $str .= wp_nonce_field( 'lj-api-import', '_wpnonce', true, false ); + $str .= wp_referer_field( false ); + $str .= ''; + $str .= '

    '; + $str .= '
    '; + + return $str; + } + + // Automatically submit the specified form after $seconds + // Include a friendly countdown in the element with id=$msg + function auto_submit( $id = 'ljapi-next-form', $msg = 'auto-message', $seconds = 10 ) { + ?>update( $wpdb->comments, + array( 'comment_karma' => 0, 'comment_agent' => 'WP LJ Importer', 'comment_type' => '' ), + array( 'comment_type' => 'livejournal-done' ) ); + $wpdb->update( $wpdb->comments, + array( 'comment_karma' => 0, 'comment_agent' => 'WP LJ Importer', 'comment_type' => '' ), + array( 'comment_type' => 'livejournal' ) ); + } + + function LJ_API_Import() { + $this->__construct(); + } + + function __construct() { + // Nothing + } +} + +$lj_api_import = new LJ_API_Import(); + +register_importer( 'livejournal', __( 'LiveJournal' ), __( 'Import posts from LiveJournal using their API.' ), array( $lj_api_import, 'dispatch' ) ); +?>