WordPress.org

Make WordPress Core

Ticket #8999: lj-api-importer.diff

File lj-api-importer.diff, 50.3 KB (added by beaulebens, 9 years ago)

My bad - use this diff instead. Had a renaming issue from dev -> diff.

  • wp-admin/admin-ajax.php

     
    11281128        $x->send();
    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'] );
    11331146        die('0');
  • wp-admin/import/livejournal.php

     
    11<?php
    22/**
    3  * LiveJournal Importer
     3 * LiveJournal API Importer
    44 *
    55 * @package WordPress
    66 * @subpackage Importer
    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 {
     22class LJ_API_Import {
    1723
    18         var $file;
     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
    26176        function footer() {
    27177                echo '</div>';
    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);
    32239                $trans_tbl = array_flip($trans_tbl);
    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;
     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                        }
    46270
    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);
     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);
     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 );
    63307                        }
     308                } while ( $num > 0 );
    64309
    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);
     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
    68324
    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);
     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';
    72357
    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 
    82                         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;
     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];
    95407                                }
     408                                add_post_meta( $post_id, 'lj_' . $prop, $val );
    96409                        }
     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' );
    97425
    98                         preg_match_all('|<comment>(.*?)</comment>|is', $post, $comments);
    99                         $comments = $comments[1];
     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.' ) );
    100435
    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);
     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                        }
    108449
    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);
     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 );
    114517
    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));
     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>';
    118699
    119                                         preg_match('|<name>(.*?)</name>|is', $comment, $comment_author);
    120                                         $comment_author = $wpdb->escape(trim($comment_author[1]));
     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];
     713                        echo '<li>';
     714                        printf( __( 'Imported comment from <strong>%s</strong> on %s' ), $comment['comment_author'], $comment['comment_date'] );
    121715
    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                                 }
     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 );
    134721                        }
    135                         if ( $num_comments ) {
    136                                 echo ' ';
    137                                 printf(__ngettext('(%s comment)', '(%s comments)', $num_comments), $num_comments);
    138                         }
     722                       
    139723                        echo '</li>';
    140724                }
     725               
     726                // Remove the file now that we're done with it
     727                @unlink( $filename );
     728
    141729                echo '</ol>';
     730               
     731                return true;
    142732        }
     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'] );
    143742
    144         function import() {
    145                 $file = wp_import_handle_upload();
    146                 if ( isset($file['error']) ) {
    147                         echo $file['error'];
    148                         return;
     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>';
    149751                }
    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>';
     752                echo '</ol>';
    161753        }
    162 
     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();
    180806                                break;
     
    183809                $this->footer();
    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();
    188884        }
     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
     1173        }
    1891174}
    1901175
    191 $livejournal_import = new LJ_Import();
     1176$lj_api_import = new LJ_API_Import();
    1921177
    193 register_importer('livejournal', __('LiveJournal'), __('Import posts from a LiveJournal XML export file.'), array ($livejournal_import, 'dispatch'));
    194 ?>
     1178register_importer( 'livejournal', __( 'LiveJournal' ), __( 'Import posts from LiveJournal using their API.' ), array( $lj_api_import, 'dispatch' ) );
     1179?>
     1180 No newline at end of file