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