Make WordPress Core

Changeset 10475


Ignore:
Timestamp:
01/31/2009 06:41:35 PM (16 years ago)
Author:
azaozz
Message:

Completely New LiveJournal Importer, props beaulebens, see #8999

Location:
trunk/wp-admin
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-admin/admin-ajax.php

    r10375 r10475  
    11291129
    11301130    break;
     1131case 'lj-importer' :
     1132    check_ajax_referer( 'lj-api-import' );
     1133    if ( !current_user_can( 'publish_posts' ) )
     1134        die('-1');
     1135    if ( empty( $_POST['step'] ) )
     1136        die( '-1' );
     1137
     1138    include( ABSPATH . 'wp-admin/import/livejournal.php' );
     1139    $result = $lj_api_import->{ 'step' . ( (int) $_POST['step'] ) }();
     1140    if ( is_wp_error( $result ) )
     1141        echo $result->get_error_message();
     1142    die;
     1143    break;
    11311144default :
    11321145    do_action( 'wp_ajax_' . $_POST['action'] );
  • trunk/wp-admin/import/livejournal.php

    r10339 r10475  
    11<?php
    22/**
    3  * LiveJournal Importer
     3 * LiveJournal API Importer
    44 *
    55 * @package WordPress
     
    77 */
    88
     9// XML-RPC library for communicating with LiveJournal API
     10require_once( ABSPATH . WPINC . '/class-IXR.php' );
     11
     12// Snoopy for getting comments (with cookies)
     13require_once( ABSPATH . WPINC . '/class-snoopy.php' );
     14
    915/**
    10  * LiveJournal Importer class
     16 * LiveJournal API Importer class
    1117 *
    12  * Imports your LiveJournal XML exported file into WordPress.
     18 * Imports your LiveJournal contents into WordPress using the LJ API
    1319 *
    14  * @since unknown
     20 * @since 2.7.1
    1521 */
    16 class LJ_Import {
    17 
    18     var $file;
     22class LJ_API_Import {
     23
     24    var $comments_url = 'http://www.livejournal.com/export_comments.bml';
     25    var $ixr_url      = 'http://www.livejournal.com/interface/xmlrpc';
     26    var $ixr;
     27    var $username;
     28    var $password;
     29    var $snoop;
     30    var $comment_meta;
     31    var $comments;
     32    var $usermap;
     33    var $postmap;
     34    var $pointers = array();
     35   
     36    // This list taken from LJ, they don't appear to have an API for it
     37    var $moods = array( '1' => 'aggravated',
     38                        '10' => 'discontent',
     39                        '100' => 'rushed',
     40                        '101' => 'contemplative',
     41                        '102' => 'nerdy',
     42                        '103' => 'geeky',
     43                        '104' => 'cynical',
     44                        '105' => 'quixotic',
     45                        '106' => 'crazy',
     46                        '107' => 'creative',
     47                        '108' => 'artistic',
     48                        '109' => 'pleased',
     49                        '11' => 'energetic',
     50                        '110' => 'bitchy',
     51                        '111' => 'guilty',
     52                        '112' => 'irritated',
     53                        '113' => 'blank',
     54                        '114' => 'apathetic',
     55                        '115' => 'dorky',
     56                        '116' => 'impressed',
     57                        '117' => 'naughty',
     58                        '118' => 'predatory',
     59                        '119' => 'dirty',
     60                        '12' => 'enraged',
     61                        '120' => 'giddy',
     62                        '121' => 'surprised',
     63                        '122' => 'shocked',
     64                        '123' => 'rejected',
     65                        '124' => 'numb',
     66                        '125' => 'cheerful',
     67                        '126' => 'good',
     68                        '127' => 'distressed',
     69                        '128' => 'intimidated',
     70                        '129' => 'crushed',
     71                        '13' => 'enthralled',
     72                        '130' => 'devious',
     73                        '131' => 'thankful',
     74                        '132' => 'grateful',
     75                        '133' => 'jealous',
     76                        '134' => 'nervous',
     77                        '14' => 'exhausted',
     78                        '15' => 'happy',
     79                        '16' => 'high',
     80                        '17' => 'horny',
     81                        '18' => 'hungry',
     82                        '19' => 'infuriated',
     83                        '2' => 'angry',
     84                        '20' => 'irate',
     85                        '21' => 'jubilant',
     86                        '22' => 'lonely',
     87                        '23' => 'moody',
     88                        '24' => 'pissed off',
     89                        '25' => 'sad',
     90                        '26' => 'satisfied',
     91                        '27' => 'sore',
     92                        '28' => 'stressed',
     93                        '29' => 'thirsty',
     94                        '3' => 'annoyed',
     95                        '30' => 'thoughtful',
     96                        '31' => 'tired',
     97                        '32' => 'touched',
     98                        '33' => 'lazy',
     99                        '34' => 'drunk',
     100                        '35' => 'ditzy',
     101                        '36' => 'mischievous',
     102                        '37' => 'morose',
     103                        '38' => 'gloomy',
     104                        '39' => 'melancholy',
     105                        '4' => 'anxious',
     106                        '40' => 'drained',
     107                        '41' => 'excited',
     108                        '42' => 'relieved',
     109                        '43' => 'hopeful',
     110                        '44' => 'amused',
     111                        '45' => 'determined',
     112                        '46' => 'scared',
     113                        '47' => 'frustrated',
     114                        '48' => 'indescribable',
     115                        '49' => 'sleepy',
     116                        '5' => 'bored',
     117                        '51' => 'groggy',
     118                        '52' => 'hyper',
     119                        '53' => 'relaxed',
     120                        '54' => 'restless',
     121                        '55' => 'disappointed',
     122                        '56' => 'curious',
     123                        '57' => 'mellow',
     124                        '58' => 'peaceful',
     125                        '59' => 'bouncy',
     126                        '6' => 'confused',
     127                        '60' => 'nostalgic',
     128                        '61' => 'okay',
     129                        '62' => 'rejuvenated',
     130                        '63' => 'complacent',
     131                        '64' => 'content',
     132                        '65' => 'indifferent',
     133                        '66' => 'silly',
     134                        '67' => 'flirty',
     135                        '68' => 'calm',
     136                        '69' => 'refreshed',
     137                        '7' => 'crappy',
     138                        '70' => 'optimistic',
     139                        '71' => 'pessimistic',
     140                        '72' => 'giggly',
     141                        '73' => 'pensive',
     142                        '74' => 'uncomfortable',
     143                        '75' => 'lethargic',
     144                        '76' => 'listless',
     145                        '77' => 'recumbent',
     146                        '78' => 'exanimate',
     147                        '79' => 'embarrassed',
     148                        '8' => 'cranky',
     149                        '80' => 'envious',
     150                        '81' => 'sympathetic',
     151                        '82' => 'sick',
     152                        '83' => 'hot',
     153                        '84' => 'cold',
     154                        '85' => 'worried',
     155                        '86' => 'loved',
     156                        '87' => 'awake',
     157                        '88' => 'working',
     158                        '89' => 'productive',
     159                        '9' => 'depressed',
     160                        '90' => 'accomplished',
     161                        '91' => 'busy',
     162                        '92' => 'blah',
     163                        '93' => 'full',
     164                        '95' => 'grumpy',
     165                        '96' => 'weird',
     166                        '97' => 'nauseated',
     167                        '98' => 'ecstatic',
     168                        '99' => 'chipper' );
    19169
    20170    function header() {
    21171        echo '<div class="wrap">';
    22172        screen_icon();
    23         echo '<h2>'.__('Import LiveJournal').'</h2>';
     173        echo '<h2>' . __( 'Import LiveJournal' ) . '</h2>';
    24174    }
    25175
     
    28178    }
    29179
     180    function greet() {
     181        ?>
     182        <div class="narrow">
     183        <form action="admin.php?import=livejournal" method="post">
     184        <?php wp_nonce_field( 'lj-api-import' ) ?>
     185        <?php if ( get_option( 'ljapi_username' ) && get_option( 'ljapi_password' ) ) : ?>
     186            <input type="hidden" name="step" value="<?php echo get_option( 'ljapi_step' ) ?>" />
     187            <p><?php _e( 'It looks like you attempted to import your LiveJournal posts previously and got interrupted.' ) ?></p>
     188            <p class="submit">
     189                <input type="submit" class="button-primary" value="<?php echo attribute_escape( __( 'Continue previous import' ) ) ?>" />
     190            </p>
     191            <p><a href="<?php echo $_SERVER['PHP_SELF'] . '?import=livejournal&amp;step=-1&amp;_wpnonce=' . wp_create_nonce( 'lj-api-import' ) . '&amp;_wp_http_referer=' . attribute_escape( $_SERVER['REQUEST_URI'] ) ?>"><?php _e( 'Cancel &amp; start a new import' ) ?></a></p>
     192            <p>
     193        <?php else : ?>
     194            <input type="hidden" name="step" value="1" />
     195            <p><?php _e( 'Howdy! This importer allows you to connect directly to LiveJournal and download all your entries and comments' ) ?></p>
     196            <p><?php _e( 'Enter your LiveJournal username and password below so we can connect to your account:' ) ?></p>
     197       
     198            <table class="form-table">
     199
     200            <tr>
     201            <th scope="row"><label for="lj_username"><?php _e( 'LiveJournal Username' ) ?></label></th>
     202            <td><input type="text" name="lj_username" id="lj_username" class="regular-text" /></td>
     203            </tr>
     204
     205            <tr>
     206            <th scope="row"><label for="lj_password"><?php _e( 'LiveJournal Password' ) ?></label></th>
     207            <td><input type="password" name="lj_password" id="lj_password" class="regular-text" /></td>
     208            </tr>
     209     
     210            </table>
     211     
     212            <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>
     213            <p><?php _e( "If you don't enter a password, ALL ENTRIES from your LiveJournal will be imported as public posts in WordPress." ) ?></p>
     214            <p><?php _e( 'Enter the password you would like to use for all protected entries here:' ) ?></p>
     215            <table class="form-table">
     216
     217            <tr>
     218            <th scope="row"><label for="protected_password"><?php _e( 'Protected Post Password' ) ?></label></th>
     219            <td><input type="text" name="protected_password" id="protected_password" class="regular-text" /></td>
     220            </tr>
     221
     222            </table>
     223
     224            <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>
     225       
     226            <p class="submit">
     227                <input type="submit" class="button-primary" value="<?php echo attribute_escape( __( 'Connect to LiveJournal and Import' ) ) ?>" />
     228            </p>
     229       
     230            <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>
     231        <?php endif; ?>
     232        </form>
     233        </div>
     234        <?php
     235    }
     236   
    30237    function unhtmlentities($string) { // From php.net for < 4.3 compat
    31238        $trans_tbl = get_html_translation_table(HTML_ENTITIES);
     
    33240        return strtr($string, $trans_tbl);
    34241    }
    35 
    36     function greet() {
    37         echo '<div class="narrow">';
    38         echo '<p>'.__('Howdy! Upload your LiveJournal XML export file and we&#8217;ll import the posts into this blog.').'</p>';
    39         echo '<p>'.__('Choose a LiveJournal XML file to upload, then click Upload file and import.').'</p>';
    40         wp_import_upload_form("admin.php?import=livejournal&amp;step=1");
    41         echo '</div>';
    42     }
    43 
     242   
    44243    function import_posts() {
    45         global $wpdb, $current_user;
    46 
    47         set_magic_quotes_runtime(0);
    48         $importdata = file($this->file); // Read the file into an array
    49         $importdata = implode('', $importdata); // squish it
    50         $importdata = str_replace(array ("\r\n", "\r"), "\n", $importdata);
    51 
    52         preg_match_all('|<entry>(.*?)</entry>|is', $importdata, $posts);
    53         $posts = $posts[1];
    54         unset($importdata);
     244        $total           = (int) get_option( 'ljapi_total' );
     245        $count           = (int) get_option( 'ljapi_count' );
     246        $lastsync        = get_option( 'ljapi_lastsync' );
     247        if ( !$lastsync ) {
     248            update_option( 'ljapi_lastsync', '1900-01-01 00:00:00' );
     249        }
     250        $sync_item_times = get_option( 'ljapi_sync_item_times' );
     251        if ( !is_array( $sync_item_times ) )
     252            $sync_item_times = array();
     253       
     254        do {
     255            $lastsync = date( 'Y-m-d H:i:s', strtotime( get_option( 'ljapi_lastsync' ) ) );
     256            $synclist = $this->lj_ixr( 'syncitems', array( 'ver' => 1, 'lastsync' => $lastsync ) );
     257            $this->log( $synclist, 'ljimport-items-' . $total . '.txt' );
     258           
     259            // Keep track of if we've downloaded everything
     260            $total = $synclist['total'];
     261            $count = $synclist['count'];
     262       
     263            foreach ( $synclist['syncitems'] as $event ) {
     264                if ( substr( $event['item'], 0, 2 ) == 'L-' ) {
     265                    $sync_item_times[ str_replace( 'L-', '', $event['item'] ) ] = $event['time'];
     266                    if ( $event['time'] > $lastsync )
     267                        $lastsync = $event['time'];
     268                }
     269            }
     270
     271            update_option( 'ljapi_sync_item_times', $sync_item_times );
     272            update_option( 'ljapi_total', $total );
     273            update_option( 'ljapi_count', $count );
     274            update_option( 'ljapi_lastsync', $lastsync );
     275        } while ( $total > $count );
     276        // endwhile - all post meta is cached locally
     277        $this->log( $sync_item_times, 'ljimport-post-mod-times.txt' );
     278       
    55279        echo '<ol>';
    56         foreach ($posts as $post) {
    57             preg_match('|<subject>(.*?)</subject>|is', $post, $post_title);
    58             $post_title = $wpdb->escape(trim($post_title[1]));
    59             if ( empty($post_title) ) {
    60                 preg_match('|<itemid>(.*?)</itemid>|is', $post, $post_title);
    61                 $post_title = $wpdb->escape(trim($post_title[1]));
    62             $post_content = preg_replace('|<lj\s+user\s*=\s*["\']([\w-]+)["\']>|', '<a href="http://$1.livejournal.com/">$1</a>', $post_content);
    63             }
    64 
    65             preg_match('|<eventtime>(.*?)</eventtime>|is', $post, $post_date);
    66             $post_date = strtotime($post_date[1]);
    67             $post_date = date('Y-m-d H:i:s', $post_date);
    68 
    69             preg_match('|<event>(.*?)</event>|is', $post, $post_content);
    70             $post_content = str_replace(array ('<![CDATA[', ']]>'), '', trim($post_content[1]));
    71             $post_content = $this->unhtmlentities($post_content);
    72 
    73             // Clean up content
    74             $post_content = preg_replace_callback('|<(/?[A-Z]+)|', create_function('$match', 'return "<" . strtolower($match[1]);'), $post_content);
    75             $post_content = str_replace('<br>', '<br />', $post_content);
    76             $post_content = str_replace('<hr>', '<hr />', $post_content);
    77             $post_content = $wpdb->escape($post_content);
    78 
    79             $post_author = $current_user->ID;
    80             $post_status = 'publish';
    81 
     280       
     281        $imported_count = (int) get_option( 'ljapi_imported_count' );
     282        $lastsync = get_option( 'ljapi_lastsync_posts' );
     283        if ( !$lastsync )
     284            update_option( 'ljapi_lastsync_posts', date( 'Y-m-d H:i:s', 0 ) );
     285       
     286        do {
     287            $lastsync = date( 'Y-m-d H:i:s', strtotime( get_option( 'ljapi_lastsync_posts' ) ) );
     288           
     289            // Get the batch of items that match up with the syncitems list
     290            $itemlist = $this->lj_ixr( 'getevents', array( 'ver' => 1,
     291                                                            'selecttype' => 'syncitems',
     292                                                            'lineendings' => 'pc',
     293                                                            'lastsync' => $lastsync ) );
     294            $this->log( $itemlist, 'ljimport-posts-' . $imported_count . '.txt' );
     295            if ( is_wp_error( $itemlist ) )
     296                return $itemlist;
     297            if ( $num = count( $itemlist['events'] ) ) {
     298                foreach ( $itemlist['events'] as $event ) {
     299                    $imported_count++;
     300                    $this->import_post( $event );
     301                    if ( $sync_item_times[ $event['itemid'] ] > $lastsync )
     302                        $lastsync = $sync_item_times[ $event['itemid'] ];
     303                }
     304                update_option( 'ljapi_lastsync_posts',  $lastsync );
     305                update_option( 'ljapi_imported_count',  $imported_count );
     306                update_option( 'ljapi_last_sync_count', $num );
     307            }
     308        } while ( $num > 0 );
     309
     310        echo '</ol>';
     311    }
     312   
     313    function import_post( $post ) {
     314        global $wpdb;
     315       
     316        // Make sure we haven't already imported this one
     317        if ( $this->get_wp_post_ID( $post['itemid'] ) )
     318            return;
     319       
     320        $user = wp_get_current_user();
     321        $post_author   = $user->ID;
     322        $post_status   = ( 'private' == trim( $post['security'] ) ) ? 'private' : 'publish'; // Only me
     323        $post_password = ( 'usemask' == trim( $post['security'] ) ) ? $this->protected_password : ''; // "Friends" via password
     324
     325        // For some reason, LJ sometimes sends a date as "2004-04-1408:38:00" (no space btwn date/time)
     326        $post_date = $post['eventtime'];
     327        if ( 18 == strlen( $post_date ) )
     328            $post_date = substr( $post_date, 0, 10 ) . ' ' . substr( $post_date, 10 );
     329       
     330        // Cleaning up and linking the title
     331        $post_title = trim( $post['subject'] );
     332        $post_title = $this->translate_lj_user( $post_title ); // Translate it, but then we'll strip the link
     333        $post_title = strip_tags( $post_title ); // Can't have tags in the title in WP
     334        $post_title = $wpdb->escape( $post_title );
     335       
     336        // Clean up content
     337        $post_content = $post['event'];
     338        $post_content = preg_replace_callback( '|<(/?[A-Z]+)|', create_function( '$match', 'return "<" . strtolower( $match[1] );' ), $post_content );
     339        // XHTMLize some tags
     340        $post_content = str_replace( '<br>', '<br />', $post_content );
     341        $post_content = str_replace( '<hr>', '<hr />', $post_content );
     342        // lj-cut ==>  <!--more-->
     343        $post_content = preg_replace( '|<lj-cut text="([^"]*)">|is', '<!--more $1-->', $post_content );
     344        $post_content = str_replace( array( '<lj-cut>', '</lj-cut>' ), array( '<!--more-->', '' ), $post_content );
     345        $first = strpos( $post_content, '<!--more' );
     346        $post_content = substr( $post_content, 0, $first + 1 ) . preg_replace( '|<!--more(.*)?-->|sUi', '', substr( $post_content, $first + 1 ) );
     347        // lj-user ==>  a href
     348        $post_content = $this->translate_lj_user( $post_content );
     349        $post_content = force_balance_tags( $post_content );
     350        $post_content = $wpdb->escape( $post_content );
     351       
     352        // Handle any tags associated with the post
     353        $tags_input = !empty( $post['props']['taglist'] ) ? $post['props']['taglist'] : '';
     354       
     355        // Check if comments are closed on this post
     356        $comment_status = !empty( $post['props']['opt_nocomments'] ) ? 'closed' : 'open';
     357
     358        echo '<li>';
     359        if ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) {
     360            printf( __( 'Post <strong>%s</strong> already exists.' ), stripslashes( $post_title ) );
     361        } else {
     362            printf( __( 'Importing post <strong>%s</strong>...' ), stripslashes( $post_title ) );
     363            $postdata = compact( 'post_author', 'post_date', 'post_content', 'post_title', 'post_status', 'post_password', 'tags_input', 'comment_status' );
     364            $post_id = wp_insert_post( $postdata );
     365            if ( is_wp_error( $post_id ) )
     366                return $post_id;
     367            if ( !$post_id ) {
     368                _e( "Couldn't get post ID" );
     369                echo '</li>';
     370                break;
     371            }
     372            $postdata['post_ID']   = $post_id;
     373            $postdata['lj_itemid'] = $post['itemid'];
     374            $this->log( $postdata, 'ljimport-post-' . $post_id . '.txt' );
     375           
     376            // Handle all the metadata for this post
     377            $this->insert_postmeta( $post_id, $post );
     378        }
     379        echo '</li>';
     380    }
     381   
     382    // Convert lj-user tags to links to that user
     383    function translate_lj_user( $str ) {
     384        return preg_replace( '|<lj\s+user\s*=\s*["\']([\w-]+)["\']>|', '<a href="http://$1.livejournal.com/" class="lj-user">$1</a>', $str );
     385    }
     386   
     387    function insert_postmeta( $post_id, $post ) {
     388        // Need the original LJ id for comments
     389        add_post_meta( $post_id, 'lj_itemid', $post['itemid'] );
     390       
     391        // And save the permalink on LJ in case we want to link back or something
     392        add_post_meta( $post_id, 'lj_permalink', $post['url'] );
     393       
     394        // Supports the following "props" from LJ, saved as lj_<prop_name> in wp_postmeta
     395        //      Adult Content - adult_content
     396        //      Location - current_coords + current_location
     397        //      Mood - current_mood (translated from current_moodid)
     398        //      Music - current_music
     399        //      Userpic - picture_keyword
     400        foreach ( array( 'adult_content', 'current_coords', 'current_location', 'current_moodid', 'current_music', 'picture_keyword' ) as $prop ) {
     401            if ( !empty( $post['props'][$prop] ) ) {
     402                if ( 'current_moodid' == $prop ) {
     403                    $prop = 'current_mood';
     404                    $val = $this->moods[ $post['props']['current_moodid'] ];
     405                } else {
     406                    $val = $post['props'][$prop];
     407                }
     408                add_post_meta( $post_id, 'lj_' . $prop, $val );
     409            }
     410        }
     411    }
     412   
     413    // Loops through and gets comment meta and content from LJ in batches
     414    // Writes raw XML files to disk for later processing
     415    function download_comments() {
     416        // Get a session via XMLRPC
     417        $cookie = $this->lj_ixr( 'sessiongenerate', array( 'ver' => 1, 'expiration' => 'short' ) );
     418       
     419        // Comment Meta
     420       
     421        // Load previous state (if any)
     422        $this->usermap = (array) get_option( 'ljapi_usermap' );
     423        $maxid         = (int) get_option( 'ljapi_maxid' ) || 1;
     424        $highest_id    = (int) get_option( 'ljapi_highest_id' );
     425
     426        // Snoopy is required to handle the cookie
     427        $this->snoop = new Snoopy();
     428        $this->snoop->cookies = $cookie;
     429       
     430        // We need to loop over the metadata request until we have it all
     431        while ( $maxid > $highest_id ) {
     432            // Now get the meta listing
     433            if ( !$this->snoop->fetch( $this->comments_url . '?get=comment_meta&startid=' . ( $highest_id + 1 ) ) )
     434                return new WP_Error( 'Snoopy', __( 'Failed to retrieve comment meta information from LiveJournal. Please try again soon.' ) );
     435
     436            // Snoopy doesn't provide an accessor for results...
     437            $results = $this->snoop->results;
     438           
     439            // Get the maxid so we know if we have them all yet
     440            preg_match( '|<maxid>(\d+)</maxid>|', $results, $matches );
     441            $maxid = !empty( $matches[1] ) ? $matches[1] : $maxid;
     442           
     443            // Parse comments and get highest id available
     444            preg_match_all( '|<comment id=\'(\d+)\'|is', $results, $matches );
     445            foreach ( $matches[1] as $id ) {
     446                if ( $id > $highest_id )
     447                    $highest_id = $id;
     448            }
     449
     450            // Parse out the list of user mappings, and add it to the known list
     451            preg_match_all( '|<usermap id=\'(\d+)\' user=\'([^\']+)\' />|', $results, $matches );
     452            foreach ( $matches[1] as $count => $userid )
     453                $this->usermap[$userid] = $matches[2][$count]; // need this in memory for translating ids => names
     454               
     455            update_option( 'ljapi_usermap',    $this->usermap );
     456            update_option( 'ljapi_maxid',      $maxid );
     457            update_option( 'ljapi_highest_id', $highest_id );
     458        }
     459        // endwhile - should have seen all comment meta at this point
     460       
     461       
     462        // Download Comment XML
     463       
     464        // Load previous state (if any)
     465        $highest_id          = (int) get_option( 'ljapi_highest_comment_id' );
     466        $comment_xml_files   = get_option( 'ljapi_comment_xml_files' );
     467        if ( !is_array( $comment_xml_files ) ) {
     468            update_option( 'ljapi_comment_xml_files', array() );
     469            $comment_xml_files = array();
     470        }
     471       
     472        echo '<ol>';
     473       
     474        // And now request the actual comments, and keep going until we have them all
     475        while ( $maxid > $highest_id ) {
     476            // Get a batch of comments, using the highest_id we've already got as a starting point
     477            if ( !$this->snoop->fetch( $this->comments_url . '?get=comment_body&startid=' . ( $highest_id + 1 ) ) )
     478                return new WP_Error( 'Snoopy', __( 'Failed to retrieve comment bodies from LiveJournal. Please try again soon.' ) );
     479           
     480            // Get the highest post ID in this batch (required for loop control)
     481            $results = $this->snoop->results;
     482            preg_match_all( '|<comment id=\'(\d+)\'|i', $results, $comments );
     483            for ( $r = 0; $r < count( $comments[1] ); $r++ ) {
     484                if ( $comments[1][$r] > $highest_id )
     485                    $highest_id = $comments[1][$r];
     486            }
     487           
     488            // $this->snoop-results is where the actual response is stored
     489            $this->log( $this->snoop->results, 'ljimport-comment-bodies-' . $highest_id . '.txt' );
     490           
     491            // Store in uploads dir. Can't use *.xml because it's not allowed
     492            $results = wp_upload_bits( 'raw-comments-' . $highest_id . '.txt', null, $results );
     493            if ( !empty( $results['error'] ) )
     494                return new WP_Error( 'xml', $results['error'] );
     495            $comment_xml_files[] = $results['file'];
     496           
     497            echo '<li>' . sprintf( __( 'Downloaded <strong>%s</strong>' ), basename( $results['file'] ) ) . '</li>';
     498            ob_flush(); flush();
     499           
     500            $comment_xml_files = array_unique( $comment_xml_files );
     501            update_option( 'ljapi_comment_xml_files', $comment_xml_files );
     502            update_option( 'ljapi_comment_xml_files_count', count( $comment_xml_files ) );
     503        }
     504        // endwhile - all comments downloaded and ready for bulk processing
     505       
     506        echo '</ol>';
     507       
     508        return true;
     509    }
     510   
     511    function parse_comment_xml( $xml_file ) {
     512        if ( !is_file( $xml_file ) || !is_readable( $xml_file ) )
     513            return new WP_Error( 'file', sprintf( __( 'Could not access comment XML file: %s'), $filename ) );
     514           
     515        // Get content from file
     516        $xml = @file_get_contents( $xml_file );
     517
     518        $cache_files = get_option( 'ljapi_comment_cache_files' );
     519        if ( !is_array( $cache_files ) )
     520            $cache_files = array();
     521       
     522        // Parse XML into comments
     523        preg_match_all( '|<comment id.*</comment>|iUs', $xml, $matches );
     524        unset( $xml );
     525        for ( $c = 0; $c < count( $matches[0] ); $c++ ) {
     526            $comment = $matches[0][$c];
     527           
     528            // Filter out any captured, deleted comments (nothing useful to import)
     529            $comment = preg_replace( '|<comment id=\'\d+\' jitemid=\'\d+\' posterid=\'\d+\' state=\'D\'[^/]*/>|is', '', $comment );
     530           
     531            // Parse this comment into an array
     532            $comment = $this->parse_comment( $comment );
     533            if ( empty( $comment['comment_post_ID'] ) )
     534                continue;
     535           
     536            // Add this comment to the appropriate cache file
     537            $filename = $this->full_path( 'ljimport-comments-' . $comment['comment_post_ID'] . '.php' );
     538            if ( $this->write_file( '<?php $comments[] = ' . var_export( $comment, true ) . '; ?>' . "\n",
     539                                $filename,
     540                                $comment['comment_post_ID'],
     541                                'a' ) )
     542            {
     543                // Keep track of files used
     544                $cache_files[] = $filename;
     545            }
     546        }
     547       
     548        // Update list of files in the DB
     549        sort( $cache_files );
     550        $cache_files = array_unique( $cache_files );
     551        update_option( 'ljapi_comment_cache_files', $cache_files );
     552        update_option( 'ljapi_comment_cache_files_count', count( $cache_files ) );
     553        $this->close_file_pointers();
     554       
     555        // Don't need this XML file anymore
     556        unlink( $xml_file );
     557       
     558        return true;
     559    }
     560   
     561    function parse_comment( $comment ) {
     562        global $wpdb;
     563       
     564        // Get the top-level attributes
     565        preg_match( '|<comment([^>]+)>|i', $comment, $attribs );
     566        preg_match( '| id=\'(\d+)\'|i', $attribs[1], $matches );
     567        $lj_comment_ID = $matches[1];
     568        preg_match( '| jitemid=\'(\d+)\'|i', $attribs[1], $matches );
     569        $lj_comment_post_ID = $matches[1];
     570        preg_match( '| posterid=\'(\d+)\'|i', $attribs[1], $matches );
     571        $comment_author_ID = $matches[1];
     572        preg_match( '| parentid=\'(\d+)\'|i', $attribs[1], $matches );
     573        $lj_comment_parent = $matches[1];
     574        preg_match( '| state=\'([SDFA])\'|i', $attribs[1], $matches );
     575        $lj_comment_state = !empty( $matches[1] ) ? $matches[1] : 'A';
     576       
     577        // Clean up "subject" - this will become the first line of the comment in WP
     578        preg_match( '|<subject>(.*)</subject>|is', $comment, $matches );
     579        $comment_subject = $wpdb->escape( trim( $matches[1] ) );
     580        if ( 'Re:' == $comment_subject )
     581            $comment_subject = '';
     582       
     583        // Get the body and HTMLize it
     584        preg_match( '|<body>(.*)</body>|is', $comment, $matches );
     585        $comment_content = !empty( $comment_subject ) ? $comment_subject . "\n\n" . $matches[1] : $matches[1];
     586        $comment_content = $this->unhtmlentities( $comment_content );
     587        $comment_content = wpautop( $comment_content );
     588        $comment_content = str_replace( '<br>', '<br />', $comment_content );
     589        $comment_content = str_replace( '<hr>', '<hr />', $comment_content );
     590        $comment_content = preg_replace_callback( '|<(/?[A-Z]+)|', create_function( '$match', 'return "<" . strtolower( $match[1] );' ), $comment_content );
     591        $comment_content = $wpdb->escape( trim( $comment_content ) );
     592       
     593        // Get and convert the date
     594        preg_match( '|<date>(.*)</date>|i', $comment, $matches );
     595        $comment_date = trim( str_replace( array( 'T', 'Z' ), ' ', $matches[1] ) );
     596       
     597        // Grab IP if available
     598        preg_match( '|<property name=\'poster_ip\'>(.*)</property>|i', $comment, $matches );
     599        $comment_author_IP = $matches[1];
     600       
     601        // Try to get something useful for the comment author, especially if it was "my" comment
     602        $author = ( substr( $this->usermap[$comment_author_ID], 0, 4 ) == 'ext_' || empty( $comment_author_ID ) ) ? __( 'Anonymous' ) : $this->usermap[$comment_author_ID];
     603        if ( get_option( 'ljapi_username' ) == $author ) {
     604            $user    = wp_get_current_user();
     605            $user_id = $user->ID;
     606            $author  = $user->display_name;
     607            $url     = trailingslashit( get_option( 'home' ) );
     608        } else {
     609            $user_id = 0;
     610            $url     = ( __( 'Anonymous' ) == $author ) ? '' : 'http://' . $author . '.livejournal.com/';           
     611        }
     612       
     613        // Send back the array of details
     614        return array( 'lj_comment_ID' => $lj_comment_ID,
     615                        'lj_comment_post_ID' => $lj_comment_post_ID,
     616                        'lj_comment_parent' => ( !empty( $lj_comment_parent ) ? $lj_comment_parent : 0 ),
     617                        'lj_comment_state' => $lj_comment_state,
     618                        'comment_post_ID' => $this->get_wp_post_ID( $lj_comment_post_ID ),
     619                        'comment_author' => $author,
     620                        'comment_author_url' => $url,
     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_agent' => 'WP LJ Importer',
     626                        'user_id' => $user_id
     627                        );
     628    }
     629   
     630   
     631    // Gets the post_ID that a LJ post has been saved as within WP
     632    function get_wp_post_ID( $post ) {
     633        global $wpdb;
     634        if ( empty( $this->postmap[$post] ) )
     635            $this->postmap[$post] = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'lj_itemid' AND meta_value = %d", $post ) );
     636        return $this->postmap[$post];
     637    }
     638   
     639    // Re-build the threading within a single cache file
     640    function thread_comments( $filename ) {
     641        if ( !is_file( $filename ) || !is_readable( $filename ) )
     642            return new WP_Error( 'File', __( sprintf( 'Cannot access file %s', $filename ) ) );
     643       
     644        $comments = array();
     645        @include( $filename );
     646        $this->comments = $comments;
     647        unset( $comments );
     648        if ( !is_array( $this->comments ) )
     649            $this->comments = array();
     650           
     651        $count = count( $this->comments );
     652        for ( $c = 0; $c < $count; $c++ ) {
     653            // Skip anything that's not "top-level" for now
     654            if ( 0 != $this->comments[$c]['lj_comment_parent'] )
     655                continue;
     656            $this->comments[$c]['children'] = $this->get_child_comments( $this->comments[$c]['lj_comment_ID'] );
     657        }
     658       
     659        // Remove anything that's not supposed to be at top level
     660        $top_comments = array();
     661        for ( $c = 0; $c < $count; $c++ ) {
     662            if ( 0 == $this->comments[$c]['lj_comment_parent'] ) {
     663                $top_comments[] = $this->comments[$c];
     664            }
     665        }
     666       
     667        // Write back to file
     668        @unlink( $filename );
     669        $this->write_file( '<?php $comments = ' . var_export( $top_comments, true ) . '; ?>', $filename, $count, 'w' );
     670        unset( $top_comments );
     671        $this->close_file_pointers();
     672       
     673        // Reference this file as being threaded
     674        $files = get_option( 'ljapi_comment_threaded_files' );
     675        $files[] = $filename;
     676        array_unique( $files );
     677        update_option( 'ljapi_comment_threaded_files', $files );
     678        update_option( 'ljapi_comment_threaded_files_count', count( $files ) );
     679       
     680        return true;
     681    }
     682   
     683    function get_child_comments( $id ) {
     684        $children = array();
     685        $count = count( $this->comments );
     686        for ( $c = 0; $c < $count; $c++ ) {
     687            // This comment is a child of the $id
     688            if ( $id == $this->comments[$c]['lj_comment_parent'] ) {
     689                $this->comments[$c]['children'] = $this->get_child_comments( $this->comments[$c]['lj_comment_ID'] );
     690                $children[] = $this->comments[$c];
     691            }
     692        }
     693        return $children;
     694    }
     695   
     696    // Inserts the contents of each cache file (should be threaded already)
     697    function insert_comments( $filename ) {
     698        echo '<ol>';
     699
     700        if ( !is_file( $filename ) || !is_readable( $filename ) )
     701            return new WP_Error( 'File', __( sprintf( 'Cannot access file %s', $filename ) ) );
     702       
     703        $comments = array();
     704        @include( $filename );
     705        $this->comments = $comments;
     706        unset( $comments );
     707        if ( !is_array( $this->comments ) )
     708            $this->comments = array();
     709           
     710        $count = count( $this->comments );
     711        for ( $c = 0; $c < $count; $c++ ) {
     712            $comment =& $this->comments[$c];
    82713            echo '<li>';
    83             if ($post_id = post_exists($post_title, $post_content, $post_date)) {
    84                 printf(__('Post <em>%s</em> already exists.'), stripslashes($post_title));
    85             } else {
    86                 printf(__('Importing post <em>%s</em>...'), stripslashes($post_title));
    87                 $postdata = compact('post_author', 'post_date', 'post_content', 'post_title', 'post_status');
    88                 $post_id = wp_insert_post($postdata);
    89                 if ( is_wp_error( $post_id ) )
    90                     return $post_id;
    91                 if (!$post_id) {
    92                     _e("Couldn't get post ID");
    93                     echo '</li>';
    94                     break;
    95                 }
    96             }
    97 
    98             preg_match_all('|<comment>(.*?)</comment>|is', $post, $comments);
    99             $comments = $comments[1];
    100 
    101             if ( $comments ) {
    102                 $comment_post_ID = (int) $post_id;
    103                 $num_comments = 0;
    104                 foreach ($comments as $comment) {
    105                     preg_match('|<event>(.*?)</event>|is', $comment, $comment_content);
    106                     $comment_content = str_replace(array ('<![CDATA[', ']]>'), '', trim($comment_content[1]));
    107                     $comment_content = $this->unhtmlentities($comment_content);
    108 
    109                     // Clean up content
    110                     $comment_content = preg_replace_callback('|<(/?[A-Z]+)|', create_function('$match', 'return "<" . strtolower($match[1]);'), $comment_content);
    111                     $comment_content = str_replace('<br>', '<br />', $comment_content);
    112                     $comment_content = str_replace('<hr>', '<hr />', $comment_content);
    113                     $comment_content = $wpdb->escape($comment_content);
    114 
    115                     preg_match('|<eventtime>(.*?)</eventtime>|is', $comment, $comment_date);
    116                     $comment_date = trim($comment_date[1]);
    117                     $comment_date = date('Y-m-d H:i:s', strtotime($comment_date));
    118 
    119                     preg_match('|<name>(.*?)</name>|is', $comment, $comment_author);
    120                     $comment_author = $wpdb->escape(trim($comment_author[1]));
    121 
    122                     preg_match('|<email>(.*?)</email>|is', $comment, $comment_author_email);
    123                     $comment_author_email = $wpdb->escape(trim($comment_author_email[1]));
    124 
    125                     $comment_approved = 1;
    126                     // Check if it's already there
    127                     if (!comment_exists($comment_author, $comment_date)) {
    128                         $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_date', 'comment_content', 'comment_approved');
    129                         $commentdata = wp_filter_comment($commentdata);
    130                         wp_insert_comment($commentdata);
    131                         $num_comments++;
    132                     }
    133                 }
    134             }
    135             if ( $num_comments ) {
    136                 echo ' ';
    137                 printf(__ngettext('(%s comment)', '(%s comments)', $num_comments), $num_comments);
    138             }
     714            printf( __( 'Imported comment from <strong>%s</strong> on %s' ), $comment['comment_author'], $comment['comment_date'] );
     715
     716            $id = wp_insert_comment( $comment );
     717            $comment['comment_ID'] = $id;
     718            if ( count( $comment['children'] ) ) {
     719                _e( ' and replies:' );
     720                $this->insert_child_comments( $comment['children'], $id );
     721            }
     722           
    139723            echo '</li>';
    140724        }
     725       
     726        // Remove the file now that we're done with it
     727        @unlink( $filename );
     728
    141729        echo '</ol>';
    142     }
    143 
    144     function import() {
    145         $file = wp_import_handle_upload();
    146         if ( isset($file['error']) ) {
    147             echo $file['error'];
    148             return;
    149         }
    150 
    151         $this->file = $file['file'];
    152         $result = $this->import_posts();
    153         if ( is_wp_error( $result ) )
    154             return $result;
    155         wp_import_cleanup($file['id']);
    156         do_action('import_done', 'livejournal');
    157 
    158         echo '<h3>';
    159         printf(__('All done. <a href="%s">Have fun!</a>'), get_option('home'));
    160         echo '</h3>';
    161     }
    162 
     730       
     731        return true;
     732    }
     733   
     734    function insert_child_comments( &$comments, $parent ) {
     735        echo '<ol>';
     736        $count = count( $comments );
     737        for ( $c = 0; $c < $count; $c++ ) {
     738            $comment =& $comments[$c];
     739            $comment['comment_parent'] = $parent;
     740            echo '<li>';
     741            printf( __( 'Imported reply from <strong>%s</strong> on %s' ), $comment['comment_author'], $comment['comment_date'] );
     742
     743            $id = wp_insert_comment( $comment );
     744            $comment['comment_ID'] = $id;
     745            if ( count( $comment['children'] ) ) {
     746                _e( ' and replies:' );
     747                $this->insert_child_comments( $comment['children'], $id );
     748            }
     749           
     750            echo '</li>';
     751        }
     752        echo '</ol>';
     753    }
     754           
     755    function lj_ixr() {
     756        if ( $challenge = $this->ixr->query( 'LJ.XMLRPC.getchallenge' ) ) {
     757            $challenge = $this->ixr->getResponse();
     758        }
     759        if ( isset( $challenge['challenge'] ) ) {
     760            $params = array( 'username' => $this->username,
     761                            'auth_method' => 'challenge',
     762                            'auth_challenge' => $challenge['challenge'],
     763                            'auth_response' => md5( $challenge['challenge'] . md5( $this->password ) ) );
     764        } else {
     765            return new WP_Error( 'IXR', __( 'LiveJournal does not appear to be responding right now. Please try again later.' ) );
     766        }
     767       
     768        $args = func_get_args();
     769        $method = array_shift( $args );
     770        if ( isset( $args[0] ) )
     771            $params = array_merge( $params, $args[0] );
     772        if ( $this->ixr->query( 'LJ.XMLRPC.' . $method, $params ) ) {
     773            return $this->ixr->getResponse();
     774        } else {
     775            $this->log( $this->ixr->message, 'ljimport-error-' . $method . '.txt' );
     776            return new WP_Error( 'IXR', __( 'XML-RPC Request Failed - ' ) . $this->ixr->getErrorCode() . ': ' . $this->ixr->getErrorMessage() );
     777        }
     778    }
     779   
    163780    function dispatch() {
    164         if (empty ($_GET['step']))
     781        if ( empty( $_REQUEST['step'] ) )
    165782            $step = 0;
    166783        else
    167             $step = (int) $_GET['step'];
     784            $step = (int) $_REQUEST['step'];
    168785
    169786        $this->header();
    170 
    171         switch ($step) {
     787       
     788        switch ( $step ) {
     789            case -1 :
     790                $this->cleanup();
     791                // Intentional no break
    172792            case 0 :
    173793                $this->greet();
    174794                break;
    175795            case 1 :
    176                 check_admin_referer('import-upload');
    177                 $result = $this->import();
     796            case 2 :
     797                $this->ixr = new IXR_Client( $this->ixr_url );
     798                // Intentional no break
     799            case 3 :
     800            case 4 :
     801            case 5 :
     802                check_admin_referer( 'lj-api-import' );
     803                $result = $this->{ 'step' . $step }();
    178804                if ( is_wp_error( $result ) )
    179805                    echo $result->get_error_message();
     
    184810    }
    185811
    186     function LJ_Import() {
    187         // Nothing.
     812    // Check form inputs and start importing posts
     813    function step1() {
     814        // Get details from form or from DB
     815        if ( !empty( $_POST['lj_username'] ) && !empty( $_POST['lj_password'] ) ) {
     816            // Store details for later
     817            $this->username = $_POST['lj_username'];
     818            $this->password = $_POST['lj_password'];
     819            update_option( 'ljapi_username', $this->username );
     820            update_option( 'ljapi_password', $this->password );
     821        } else {
     822            $this->username = get_option( 'ljapi_username' );
     823            $this->password = get_option( 'ljapi_password' );
     824        }
     825   
     826        // This is the password to set on protected posts
     827        if ( !empty( $_POST['protected_password'] ) ) {
     828            $this->protected_password = $_POST['protected_password'];
     829            update_option( 'ljapi_protected_password', $this->protected_password );
     830        } else {
     831            $this->protected_password = get_option( 'ljapi_protected_password' );
     832        }
     833       
     834        // Login to confirm the details are correct
     835        if ( empty( $this->username ) || empty( $this->password ) ) {
     836            ?>
     837            <p><?php _e( 'Please enter your LiveJournal username <em>and</em> password so we can download your posts and comments.' ) ?></p>
     838            <p><a href="<?php echo $_SERVER['PHP_SELF'] . '?import=livejournal&amp;step=-1&amp;_wpnonce=' . wp_create_nonce( 'lj-api-import' ) . '&amp;_wp_http_referer=' . attribute_escape( str_replace( '&step=1', '', $_SERVER['REQUEST_URI'] ) ) ?>"><?php _e( 'Start again' ) ?></a></p>
     839            <?php
     840            return;
     841        }
     842        $login = $this->lj_ixr( 'login' );
     843        if ( is_wp_error( $login ) ) {
     844            if ( 100 == $this->ixr->getErrorCode() || 101 == $this->ixr->getErrorCode() ) {
     845                ?>
     846                <p><?php _e( 'Logging in to LiveJournal failed. Check your username and password and try again.' ) ?></p>
     847                <p><a href="<?php echo $_SERVER['PHP_SELF'] . '?import=livejournal&amp;step=-1&amp;_wpnonce=' . wp_create_nonce( 'lj-api-import' ) . '&amp;_wp_http_referer=' . attribute_escape( str_replace( '&step=1', '', $_SERVER['REQUEST_URI'] ) ) ?>"><?php _e( 'Start again' ) ?></a></p>
     848                <?php
     849                return;
     850            } else {
     851                return $login;
     852            }
     853        }
     854       
     855        // Set up some options to avoid them autoloading (these ones get big)
     856        add_option( 'ljapi_sync_item_times',        '', '', 'no' );
     857        add_option( 'ljapi_usermap',                '', '', 'no' );
     858        add_option( 'ljapi_comment_xml_files',      '', '', 'no' );
     859        add_option( 'ljapi_comment_cache_files',    '', '', 'no' );
     860        add_option( 'ljapi_comment_threaded_files', '', '', 'no' );
     861       
     862        echo '<h3>' . __( 'Importing Posts' ) . '</h3>';
     863        echo '<p>' . __( "We're downloading and importing all your LiveJournal posts..." ) . '</p>';
     864        ob_flush(); flush();
     865       
     866        // Now do the grunt work
     867        set_time_limit( 0 );
     868        $result = $this->import_posts();
     869        if ( is_wp_error( $result ) ) {
     870            if ( 406 == $this->ixr->getErrorCode() ) {
     871                ?>
     872                <p><strong><?php _e( 'Uh oh &ndash; LiveJournal has disconnected us because we made too many requests to their servers too quickly.' ) ?></strong></p>
     873                <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>
     874                <?php
     875                return;
     876            } else {
     877                return $result;
     878            }
     879        }
     880       
     881        echo '<p>' . __( "Your posts have all been imported, but wait - there's more! Now we need to process &amp; import your comments." ) . '</p>';
     882        echo $this->next_step( 2, __( 'Download my comments &raquo;' ) );
     883        $this->auto_submit();
     884    }
     885   
     886    // Download comments to local XML
     887    function step2() {
     888        set_time_limit( 0 );
     889        update_option( 'ljapi_step', 2 );
     890        $this->username = get_option( 'ljapi_username' );
     891        $this->password = get_option( 'ljapi_password' );
     892       
     893        echo '<h3>' . __( 'Downloading Comments' ) . '</h3>';
     894        echo '<p>' . __( 'Now we will download your comments so we can process and import them...' ) . '</p>';
     895        ob_flush(); flush();
     896       
     897        $result = $this->download_comments();
     898        if ( is_wp_error( $result ) )
     899            return $result;
     900
     901        echo '<p>' . __( 'Your comments have all been downloaded to this server now, so we can process them and get them ready for importing.' ) . '</p>';
     902        echo $this->next_step( 3, __( 'Process my comment files &raquo;' ) );
     903        $this->auto_submit();
     904    }
     905
     906    // Parse XML into comment cache files   
     907    function step3() {
     908
     909        set_time_limit( 0 );
     910        update_option( 'ljapi_step', 3 );
     911       
     912        $this->usermap = get_option( 'ljapi_usermap' );
     913
     914        echo '<div id="ljapi-status">';
     915        echo '<h3>' . __( 'Parsing Comments' ) . '</h3>';
     916        echo '<p>' . __( 'Time to clean up your comments and get them into a format WordPress understands...' ) . '</p>';
     917        ob_flush(); flush();
     918       
     919        $files = get_option( 'ljapi_comment_xml_files' );
     920        if ( count( $files ) ) {
     921            $file = array_pop( $files );
     922       
     923            $result = $this->parse_comment_xml( $file );
     924            if ( is_wp_error( $result ) )
     925                return $result;
     926
     927            update_option( 'ljapi_comment_xml_files', $files );
     928        }
     929       
     930        if ( count( $files ) ) {
     931            ?>
     932                <form action="admin.php?import=livejournal" method="post" id="ljapi-auto-repost">
     933                <p><strong><?php printf( __( 'Processed comment file %d of %d' ), ( get_option( 'ljapi_comment_xml_files_count' ) - count( $files ) ), get_option( 'ljapi_comment_xml_files_count' ) ) ?></strong></p>
     934                <?php wp_nonce_field( 'lj-api-import' ) ?>
     935                <input type="hidden" name="step" id="step" value="3" />
     936                <p><input type="submit" class="button-primary" value="<?php echo attribute_escape( __( 'Process the next comment file &raquo;' ) ) ?>" /> <span id="auto-message"></span></p>
     937                </form>
     938                <?php $this->auto_ajax( 'ljapi-auto-repost', 'auto-message', 0 ); ?>
     939            <?php
     940        } else {
     941            echo '<p>' . __( 'Yay, we finished processing all of your comment files! Now we need to re-build your conversation threads.' ) . '</p>';
     942            echo $this->next_step( 4, __( 'Thread my comments &raquo;' ) );
     943            $this->auto_submit();
     944        }
     945        echo '</div>';
     946    }
     947
     948    // Thread comments within their cache files
     949    function step4() {
     950        set_time_limit( 0 );
     951        update_option( 'ljapi_step', 4 );
     952       
     953        echo '<div id="ljapi-status">';
     954        echo '<h3>' . __( 'Threading Comments' ) . '</h3>';
     955        echo '<p>' . __( 'Re-building your conversation threads ready for import...' ) . '</p>';
     956        ob_flush(); flush();
     957       
     958        $files = get_option( 'ljapi_comment_cache_files' );
     959        if ( count( $files ) ) {
     960            $file = array_pop( $files );
     961       
     962            $result = $this->thread_comments( $file );
     963            if ( is_wp_error( $result ) )
     964                return $result;
     965           
     966            update_option( 'ljapi_comment_cache_files', $files );
     967        }
     968       
     969        if ( count( $files ) ) {
     970            ?>
     971                <form action="admin.php?import=livejournal" method="post" id="ljapi-auto-repost">
     972                <p><strong><?php printf( __( 'Threaded cache file %d of %d' ), ( get_option( 'ljapi_comment_cache_files_count' ) - count( $files ) ), get_option( 'ljapi_comment_cache_files_count' ) ) ?></strong></p>
     973                <?php wp_nonce_field( 'lj-api-import' ) ?>
     974                <input type="hidden" name="step" id="step" value="4" />
     975                <p><input type="submit" class="button-primary" value="<?php echo attribute_escape( __( 'Thread the next cache file &raquo;' ) ) ?>" /> <span id="auto-message"></span></p>
     976                </form>
     977                <?php $this->auto_ajax( 'ljapi-auto-repost', 'auto-message', 0 ); ?>
     978            <?php
     979        } else {
     980            echo '<p>' . __( "Alrighty, your comments are all threaded. There's just one last step -- time to actually import them all now!" ) . '</p>';
     981            echo '<p>' . __( 'This last part in particular can take a really long time if you have a lot of comments. You might want to go and do something else while you wait.' ) . '</p>';
     982            echo $this->next_step( 5, __( 'Import my threaded comments into WordPress &raquo;' ) );
     983            $this->auto_submit();
     984        }
     985        echo '</div>';
     986    }
     987
     988    // Import comments from cache files into WP
     989    function step5() {
     990        set_time_limit( 0 );
     991        update_option( 'ljapi_step', 5 );
     992       
     993       
     994        echo '<div id="ljapi-status">';
     995        echo '<h3>' . __( 'Importing Comments' ) . '</h3>';
     996        echo '<p>' . __( 'This is the big one -- we are now inserting your comment threads into WordPress...' ) . '</p>';
     997       
     998        $files = get_option( 'ljapi_comment_threaded_files' );
     999        echo '<p><strong>' . sprintf( __( 'Importing cache file %d of %d' ), ( get_option( 'ljapi_comment_threaded_files_count' ) - count( $files ) + 1 ), get_option( 'ljapi_comment_threaded_files_count' ) ) . '</strong></p>';
     1000        ob_flush(); flush();
     1001       
     1002        if ( count( $files ) ) {
     1003            $file = array_pop( $files );
     1004       
     1005            $result = $this->insert_comments( $file );
     1006            if ( is_wp_error( $result ) )
     1007                return $result;
     1008           
     1009            update_option( 'ljapi_comment_threaded_files', $files );
     1010        }
     1011       
     1012        if ( count( $files ) ) {
     1013            ?>
     1014                <form action="admin.php?import=livejournal" method="post" id="ljapi-auto-repost">
     1015                <?php wp_nonce_field( 'lj-api-import' ) ?>
     1016                <input type="hidden" name="step" id="step" value="5" />
     1017                <p><input type="submit" class="button-primary" value="<?php echo attribute_escape( __( 'Import the next cache file &raquo;' ) ) ?>" /> <span id="auto-message"></span></p>
     1018                </form>
     1019                <?php $this->auto_ajax( 'ljapi-auto-repost', 'auto-message', 0 ); ?>
     1020            <?php
     1021        } else {
     1022            // Clean up database and we're out
     1023            $this->cleanup();
     1024            do_action( 'import_done', 'livejournal' );
     1025            echo '<h3>';
     1026            printf( __( 'All done. <a href="%s">Have fun!</a>' ), get_option( 'home' ) );
     1027            echo '</h3>';
     1028        }
     1029        echo '</div>';
     1030    }
     1031   
     1032    // Returns the HTML for a link to the next page
     1033    function next_step( $next_step, $label, $id = 'ljapi-next-form' ) {
     1034        $str  = '<form action="admin.php?import=livejournal" method="post" id="' . $id . '">';
     1035        $str .= wp_nonce_field( 'lj-api-import', '_wpnonce', true, false );
     1036        $str .= wp_referer_field( false );
     1037        $str .= '<input type="hidden" name="step" id="step" value="' . $next_step . '" />';
     1038        $str .= '<p><input type="submit" class="button-primary" value="' . attribute_escape( $label ) . '" /> <span id="auto-message"></span></p>';
     1039        $str .= '</form>';
     1040       
     1041        return $str;
     1042    }
     1043
     1044    // Automatically submit the form with #id to continue the process
     1045    // Hide any submit buttons to avoid people clicking them
     1046    // Display a countdown in the element indicated by $msg for "Continuing in x"
     1047    function auto_ajax( $id = 'ljapi-next-form', $msg = 'auto-message', $seconds = 5 ) {
     1048        ?><script type="text/javascript">
     1049            next_counter = <?php echo $seconds ?>;
     1050            jQuery(document).ready(function(){
     1051                ljapi_msg();
     1052            });
     1053           
     1054            function ljapi_msg() {
     1055                str = '<?php _e( "Continuing in %d" ) ?>';
     1056                jQuery( '#<?php echo $msg ?>' ).text( str.replace( /%d/, next_counter ) );
     1057                if ( next_counter <= 0 ) {
     1058                    if ( jQuery( '#<?php echo $id ?>' ).length ) {
     1059                        jQuery( "#<?php echo $id ?> input[type='submit']" ).hide();
     1060                        jQuery.ajaxSetup({'timeout':3600000});
     1061                        str = '<?php _e( "Processing next file." ) ?> <img src="images/loading-publish.gif" alt="" id="processing" align="top" />';
     1062                        jQuery( '#<?php echo $msg ?>' ).html( str );
     1063                        jQuery('#ljapi-status').load(ajaxurl, {'action':'lj-importer',
     1064                                                                'step':jQuery('#step').val(),
     1065                                                                '_wpnonce':'<?php echo wp_create_nonce( 'lj-api-import' ) ?>',
     1066                                                                '_wp_http_referer':'<?php echo $_SERVER['REQUEST_URI'] ?>'});
     1067                        return;
     1068                    }
     1069                }
     1070                next_counter = next_counter - 1;
     1071                setTimeout('ljapi_msg()', 1000);
     1072            }
     1073        </script><?php
     1074    }
     1075   
     1076    // Automatically submit the specified form after $seconds
     1077    // Include a friendly countdown in the element with id=$msg
     1078    function auto_submit( $id = 'ljapi-next-form', $msg = 'auto-message', $seconds = 10 ) {
     1079        ?><script type="text/javascript">
     1080            next_counter = <?php echo $seconds ?>;
     1081            jQuery(document).ready(function(){
     1082                ljapi_msg();
     1083            });
     1084           
     1085            function ljapi_msg() {
     1086                str = '<?php _e( "Continuing in %d" ) ?>';
     1087                jQuery( '#<?php echo $msg ?>' ).text( str.replace( /%d/, next_counter ) );
     1088                if ( next_counter <= 0 ) {
     1089                    if ( jQuery( '#<?php echo $id ?>' ).length ) {
     1090                        jQuery( "#<?php echo $id ?> input[type='submit']" ).hide();
     1091                        str = '<?php _e( "Continuing" ) ?> <img src="images/loading-publish.gif" alt="" id="processing" align="top" />';
     1092                        jQuery( '#<?php echo $msg ?>' ).html( str );
     1093                        jQuery( '#<?php echo $id ?>' ).submit();
     1094                        return;
     1095                    }
     1096                }
     1097                next_counter = next_counter - 1;
     1098                setTimeout('ljapi_msg()', 1000);
     1099            }
     1100        </script><?php
     1101    }
     1102
     1103    // Remove all options used during import process
     1104    function cleanup() {
     1105        delete_option( 'ljapi_username' );
     1106        delete_option( 'ljapi_password' );
     1107        delete_option( 'ljapi_protected_password' );
     1108        delete_option( 'ljapi_total' );
     1109        delete_option( 'ljapi_count' );
     1110        delete_option( 'ljapi_lastsync' );
     1111        delete_option( 'ljapi_last_sync_count' );
     1112        delete_option( 'ljapi_sync_item_times' );
     1113        delete_option( 'ljapi_lastsync_posts' );
     1114        delete_option( 'ljapi_imported_count' );
     1115        delete_option( 'ljapi_maxid' );
     1116        delete_option( 'ljapi_usermap' );
     1117        delete_option( 'ljapi_highest_id' );
     1118        delete_option( 'ljapi_highest_comment_id' );
     1119        delete_option( 'ljapi_comment_xml_files' );
     1120        delete_option( 'ljapi_comment_xml_files_count' );
     1121        delete_option( 'ljapi_comment_cache_files' );
     1122        delete_option( 'ljapi_comment_cache_files_count' );
     1123        delete_option( 'ljapi_comment_threaded_files' );
     1124        delete_option( 'ljapi_comment_threaded_files_count' );
     1125        delete_option( 'ljapi_step' );
     1126    }
     1127   
     1128    // Dump a string to a log file (appends to existing file)
     1129    function log( $string, $name ) {
     1130        return; // remove this to enable "debugging" output to files in /wp-content/ljimport
     1131        $path = wp_upload_dir();
     1132        $path = $path['path'];
     1133        if ( get_option( 'uploads_use_yearmonth_folders' ) )
     1134            $path = substr( $path, 0, -8 );
     1135
     1136        if ( !is_dir( $path . '/ljimport' ) )
     1137            mkdir( $path . '/ljimport' );
     1138           
     1139        $fh = @fopen( $path . '/ljimport/' . $name, 'a' );
     1140        if ( $fh ) {
     1141            if ( is_array( $string ) || is_object( $string ) )
     1142                fwrite( $fh, var_export( $string, true ) . "\n\n" );
     1143            else
     1144                fwrite( $fh, $string . "\n\n" );
     1145            fclose( $fh );
     1146        }
     1147    }
     1148   
     1149    function write_file( $data, $name, $id, $mode = 'a' ) {
     1150        if ( empty( $this->pointers[$id] ) )
     1151            $this->pointers[$id] = @fopen( $name, $mode );
     1152        if ( $this->pointers[$id] )
     1153            return fwrite( $this->pointers[$id], $data );
     1154        return false;
     1155    }
     1156   
     1157    function full_path( $basename ) {
     1158        $uploads = wp_upload_dir();
     1159        return $uploads['path'] . '/' . $basename;
     1160    }
     1161   
     1162    function close_file_pointers() {
     1163        foreach ( $this->pointers as $p )
     1164            @fclose( $p );
     1165    }
     1166
     1167    function LJ_API_Import() {
     1168        $this->__construct();
     1169    }
     1170   
     1171    function __construct() {
     1172        // Nothing
    1881173    }
    1891174}
    1901175
    191 $livejournal_import = new LJ_Import();
    192 
    193 register_importer('livejournal', __('LiveJournal'), __('Import posts from a LiveJournal XML export file.'), array ($livejournal_import, 'dispatch'));
     1176$lj_api_import = new LJ_API_Import();
     1177
     1178register_importer( 'livejournal', __( 'LiveJournal' ), __( 'Import posts from LiveJournal using their API.' ), array( $lj_api_import, 'dispatch' ) );
    1941179?>
Note: See TracChangeset for help on using the changeset viewer.