web/wp-admin/import/blogger.php
branchwordpress
changeset 109 03b0d1493584
child 132 4d4862461b8d
equal deleted inserted replaced
-1:000000000000 109:03b0d1493584
       
     1 <?php
       
     2 /**
       
     3  * Blogger Importer
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage Importer
       
     7  */
       
     8 
       
     9 /**
       
    10  * How many records per GData query
       
    11  *
       
    12  * @package WordPress
       
    13  * @subpackage Blogger_Import
       
    14  * @var int
       
    15  * @since unknown
       
    16  */
       
    17 define( 'MAX_RESULTS',        50 );
       
    18 
       
    19 /**
       
    20  * How many seconds to let the script run
       
    21  *
       
    22  * @package WordPress
       
    23  * @subpackage Blogger_Import
       
    24  * @var int
       
    25  * @since unknown
       
    26  */
       
    27 define( 'MAX_EXECUTION_TIME', 20 );
       
    28 
       
    29 /**
       
    30  * How many seconds between status bar updates
       
    31  *
       
    32  * @package WordPress
       
    33  * @subpackage Blogger_Import
       
    34  * @var int
       
    35  * @since unknown
       
    36  */
       
    37 define( 'STATUS_INTERVAL',     3 );
       
    38 
       
    39 /**
       
    40  * Blogger Importer class
       
    41  *
       
    42  * @since unknown
       
    43  */
       
    44 class Blogger_Import {
       
    45 
       
    46 	// Shows the welcome screen and the magic auth link.
       
    47 	function greet() {
       
    48 		$next_url = get_option('siteurl') . '/wp-admin/index.php?import=blogger&amp;noheader=true';
       
    49 		$auth_url = "https://www.google.com/accounts/AuthSubRequest";
       
    50 		$title = __('Import Blogger');
       
    51 		$welcome = __('Howdy! This importer allows you to import posts and comments from your Blogger account into your WordPress blog.');
       
    52 		$prereqs = __('To use this importer, you must have a Google account and an upgraded (New, was Beta) blog hosted on blogspot.com or a custom domain (not FTP).');
       
    53 		$stepone = __('The first thing you need to do is tell Blogger to let WordPress access your account. You will be sent back here after providing authorization.');
       
    54 		$auth = esc_attr__('Authorize');
       
    55 
       
    56 		echo "
       
    57 		<div class='wrap'>
       
    58 		".screen_icon()."
       
    59 		<h2>$title</h2>
       
    60 		<p>$welcome</p><p>$prereqs</p><p>$stepone</p>
       
    61 			<form action='$auth_url' method='get'>
       
    62 				<p class='submit' style='text-align:left;'>
       
    63 					<input type='submit' class='button' value='$auth' />
       
    64 					<input type='hidden' name='scope' value='http://www.blogger.com/feeds/' />
       
    65 					<input type='hidden' name='session' value='1' />
       
    66 					<input type='hidden' name='secure' value='0' />
       
    67 					<input type='hidden' name='next' value='$next_url' />
       
    68 				</p>
       
    69 			</form>
       
    70 		</div>\n";
       
    71 	}
       
    72 
       
    73 	function uh_oh($title, $message, $info) {
       
    74 		echo "<div class='wrap'>";
       
    75 		screen_icon();
       
    76 		echo "<h2>$title</h2><p>$message</p><pre>$info</pre></div>";
       
    77 	}
       
    78 
       
    79 	function auth() {
       
    80 		// We have a single-use token that must be upgraded to a session token.
       
    81 		$token = preg_replace( '/[^-_0-9a-zA-Z]/', '', $_GET['token'] );
       
    82 		$headers = array(
       
    83 			"GET /accounts/AuthSubSessionToken HTTP/1.0",
       
    84 			"Authorization: AuthSub token=\"$token\""
       
    85 		);
       
    86 		$request = join( "\r\n", $headers ) . "\r\n\r\n";
       
    87 		$sock = $this->_get_auth_sock( );
       
    88 		if ( ! $sock ) return false;
       
    89 		$response = $this->_txrx( $sock, $request );
       
    90 		preg_match( '/token=([-_0-9a-z]+)/i', $response, $matches );
       
    91 		if ( empty( $matches[1] ) ) {
       
    92 			$this->uh_oh(
       
    93 				__( 'Authorization failed' ),
       
    94 				__( 'Something went wrong. If the problem persists, send this info to support:' ),
       
    95 				htmlspecialchars($response)
       
    96 			);
       
    97 			return false;
       
    98 		}
       
    99 		$this->token = $matches[1];
       
   100 
       
   101 		wp_redirect( remove_query_arg( array( 'token', 'noheader' ) ) );
       
   102 	}
       
   103 
       
   104 	function get_token_info() {
       
   105 		$headers = array(
       
   106 			"GET /accounts/AuthSubTokenInfo  HTTP/1.0",
       
   107 			"Authorization: AuthSub token=\"$this->token\""
       
   108 		);
       
   109 		$request = join( "\r\n", $headers ) . "\r\n\r\n";
       
   110 		$sock = $this->_get_auth_sock( );
       
   111 		if ( ! $sock ) return;
       
   112 		$response = $this->_txrx( $sock, $request );
       
   113 		return $this->parse_response($response);
       
   114 	}
       
   115 
       
   116 	function token_is_valid() {
       
   117 		$info = $this->get_token_info();
       
   118 
       
   119 		if ( $info['code'] == 200 )
       
   120 			return true;
       
   121 
       
   122 		return false;
       
   123 	}
       
   124 
       
   125 	function show_blogs($iter = 0) {
       
   126 		if ( empty($this->blogs) ) {
       
   127 			$headers = array(
       
   128 				"GET /feeds/default/blogs HTTP/1.0",
       
   129 				"Host: www.blogger.com",
       
   130 				"Authorization: AuthSub token=\"$this->token\""
       
   131 			);
       
   132 			$request = join( "\r\n", $headers ) . "\r\n\r\n";
       
   133 			$sock = $this->_get_blogger_sock( );
       
   134 			if ( ! $sock ) return;
       
   135 			$response = $this->_txrx( $sock, $request );
       
   136 
       
   137 			// Quick and dirty XML mining.
       
   138 			list( $headers, $xml ) = explode( "\r\n\r\n", $response );
       
   139 			$p = xml_parser_create();
       
   140 			xml_parse_into_struct($p, $xml, $vals, $index);
       
   141 			xml_parser_free($p);
       
   142 
       
   143 			$this->title = $vals[$index['TITLE'][0]]['value'];
       
   144 
       
   145 			// Give it a few retries... this step often flakes out the first time.
       
   146 			if ( empty( $index['ENTRY'] ) ) {
       
   147 				if ( $iter < 3 ) {
       
   148 					return $this->show_blogs($iter + 1);
       
   149 				} else {
       
   150 					$this->uh_oh(
       
   151 						__('Trouble signing in'),
       
   152 						__('We were not able to gain access to your account. Try starting over.'),
       
   153 						''
       
   154 					);
       
   155 					return false;
       
   156 				}
       
   157 			}
       
   158 
       
   159 			foreach ( $index['ENTRY'] as $i ) {
       
   160 				$blog = array();
       
   161 				while ( ( $tag = $vals[$i] ) && ! ( $tag['tag'] == 'ENTRY' && $tag['type'] == 'close' ) ) {
       
   162 					if ( $tag['tag'] == 'TITLE' ) {
       
   163 						$blog['title'] = $tag['value'];
       
   164 					} elseif ( $tag['tag'] == 'SUMMARY' ) {
       
   165 						$blog['summary'] == $tag['value'];
       
   166 					} elseif ( $tag['tag'] == 'LINK' ) {
       
   167 						if ( $tag['attributes']['REL'] == 'alternate' && $tag['attributes']['TYPE'] == 'text/html' ) {
       
   168 							$parts = parse_url( $tag['attributes']['HREF'] );
       
   169 							$blog['host'] = $parts['host'];
       
   170 						} elseif ( $tag['attributes']['REL'] == 'edit' )
       
   171 							$blog['gateway'] = $tag['attributes']['HREF'];
       
   172 					}
       
   173 					++$i;
       
   174 				}
       
   175 				if ( ! empty ( $blog ) ) {
       
   176 					$blog['total_posts'] = $this->get_total_results('posts', $blog['host']);
       
   177 					$blog['total_comments'] = $this->get_total_results('comments', $blog['host']);
       
   178 					$blog['mode'] = 'init';
       
   179 					$this->blogs[] = $blog;
       
   180 				}
       
   181 			}
       
   182 
       
   183 			if ( empty( $this->blogs ) ) {
       
   184 				$this->uh_oh(
       
   185 					__('No blogs found'),
       
   186 					__('We were able to log in but there were no blogs. Try a different account next time.'),
       
   187 					''
       
   188 				);
       
   189 				return false;
       
   190 			}
       
   191 		}
       
   192 //echo '<pre>'.print_r($this,1).'</pre>';
       
   193 		$start    = esc_js( __('Import') );
       
   194 		$continue = esc_js( __('Continue') );
       
   195 		$stop     = esc_js( __('Importing...') );
       
   196 		$authors  = esc_js( __('Set Authors') );
       
   197 		$loadauth = esc_js( __('Preparing author mapping form...') );
       
   198 		$authhead = esc_js( __('Final Step: Author Mapping') );
       
   199 		$nothing  = esc_js( __('Nothing was imported. Had you already imported this blog?') );
       
   200 		$title    = __('Blogger Blogs');
       
   201 		$name     = __('Blog Name');
       
   202 		$url      = __('Blog URL');
       
   203 		$action   = __('The Magic Button');
       
   204 		$posts    = __('Posts');
       
   205 		$comments = __('Comments');
       
   206 		$noscript = __('This feature requires Javascript but it seems to be disabled. Please enable Javascript and then reload this page. Don&#8217;t worry, you can turn it back off when you&#8217;re done.');
       
   207 
       
   208 		$interval = STATUS_INTERVAL * 1000;
       
   209 
       
   210 		foreach ( $this->blogs as $i => $blog ) {
       
   211 			if ( $blog['mode'] == 'init' )
       
   212 				$value = $start;
       
   213 			elseif ( $blog['mode'] == 'posts' || $blog['mode'] == 'comments' )
       
   214 				$value = $continue;
       
   215 			else
       
   216 				$value = $authors;
       
   217 			$value = esc_attr($value);
       
   218 			$blogtitle = esc_js( $blog['title'] );
       
   219 			$pdone = isset($blog['posts_done']) ? (int) $blog['posts_done'] : 0;
       
   220 			$cdone = isset($blog['comments_done']) ? (int) $blog['comments_done'] : 0;
       
   221 			$init .= "blogs[$i]=new blog($i,'$blogtitle','{$blog['mode']}'," . $this->get_js_status($i) . ');';
       
   222 			$pstat = "<div class='ind' id='pind$i'>&nbsp;</div><div id='pstat$i' class='stat'>$pdone/{$blog['total_posts']}</div>";
       
   223 			$cstat = "<div class='ind' id='cind$i'>&nbsp;</div><div id='cstat$i' class='stat'>$cdone/{$blog['total_comments']}</div>";
       
   224 			$rows .= "<tr id='blog$i'><td class='blogtitle'>$blogtitle</td><td class='bloghost'>{$blog['host']}</td><td class='bar'>$pstat</td><td class='bar'>$cstat</td><td class='submit'><input type='submit' class='button' id='submit$i' value='$value' /><input type='hidden' name='blog' value='$i' /></td></tr>\n";
       
   225 		}
       
   226 
       
   227 		echo "<div class='wrap'><h2>$title</h2><noscript>$noscript</noscript><table cellpadding='5px'><thead><tr><td>$name</td><td>$url</td><td>$posts</td><td>$comments</td><td>$action</td></tr></thead>\n$rows</table></div>";
       
   228 		echo "
       
   229 		<script type='text/javascript'>
       
   230 		/* <![CDATA[ */
       
   231 			var strings = {cont:'$continue',stop:'$stop',stopping:'$stopping',authors:'$authors',nothing:'$nothing'};
       
   232 			var blogs = {};
       
   233 			function blog(i, title, mode, status){
       
   234 				this.blog   = i;
       
   235 				this.mode   = mode;
       
   236 				this.title  = title;
       
   237 				this.status = status;
       
   238 				this.button = document.getElementById('submit'+this.blog);
       
   239 			};
       
   240 			blog.prototype = {
       
   241 				start: function() {
       
   242 					this.cont = true;
       
   243 					this.kick();
       
   244 					this.check();
       
   245 				},
       
   246 				kick: function() {
       
   247 					++this.kicks;
       
   248 					var i = this.blog;
       
   249 					jQuery.post('admin.php?import=blogger&noheader=true',{blog:this.blog},function(text,result){blogs[i].kickd(text,result)});
       
   250 				},
       
   251 				check: function() {
       
   252 					++this.checks;
       
   253 					var i = this.blog;
       
   254 					jQuery.post('admin.php?import=blogger&noheader=true&status=true',{blog:this.blog},function(text,result){blogs[i].checkd(text,result)});
       
   255 				},
       
   256 				kickd: function(text, result) {
       
   257 					if ( result == 'error' ) {
       
   258 						// TODO: exception handling
       
   259 						if ( this.cont )
       
   260 							setTimeout('blogs['+this.blog+'].kick()', 1000);
       
   261 					} else {
       
   262 						if ( text == 'done' ) {
       
   263 							this.stop();
       
   264 							this.done();
       
   265 						} else if ( text == 'nothing' ) {
       
   266 							this.stop();
       
   267 							this.nothing();
       
   268 						} else if ( text == 'continue' ) {
       
   269 							this.kick();
       
   270 						} else if ( this.mode = 'stopped' )
       
   271 							jQuery(this.button).attr('value', strings.cont);
       
   272 					}
       
   273 					--this.kicks;
       
   274 				},
       
   275 				checkd: function(text, result) {
       
   276 					if ( result == 'error' ) {
       
   277 						// TODO: exception handling
       
   278 					} else {
       
   279 						eval('this.status='+text);
       
   280 						jQuery('#pstat'+this.blog).empty().append(this.status.p1+'/'+this.status.p2);
       
   281 						jQuery('#cstat'+this.blog).empty().append(this.status.c1+'/'+this.status.c2);
       
   282 						this.update();
       
   283 						if ( this.cont || this.kicks > 0 )
       
   284 							setTimeout('blogs['+this.blog+'].check()', $interval);
       
   285 					}
       
   286 					--this.checks;
       
   287 				},
       
   288 				update: function() {
       
   289 					jQuery('#pind'+this.blog).width(((this.status.p1>0&&this.status.p2>0)?(this.status.p1/this.status.p2*jQuery('#pind'+this.blog).parent().width()):1)+'px');
       
   290 					jQuery('#cind'+this.blog).width(((this.status.c1>0&&this.status.c2>0)?(this.status.c1/this.status.c2*jQuery('#cind'+this.blog).parent().width()):1)+'px');
       
   291 				},
       
   292 				stop: function() {
       
   293 					this.cont = false;
       
   294 				},
       
   295 				done: function() {
       
   296 					this.mode = 'authors';
       
   297 					jQuery(this.button).attr('value', strings.authors);
       
   298 				},
       
   299 				nothing: function() {
       
   300 					this.mode = 'nothing';
       
   301 					jQuery(this.button).remove();
       
   302 					alert(strings.nothing);
       
   303 				},
       
   304 				getauthors: function() {
       
   305 					if ( jQuery('div.wrap').length > 1 )
       
   306 						jQuery('div.wrap').gt(0).remove();
       
   307 					jQuery('div.wrap').empty().append('<h2>$authhead</h2><h3>' + this.title + '</h3>');
       
   308 					jQuery('div.wrap').append('<p id=\"auth\">$loadauth</p>');
       
   309 					jQuery('p#auth').load('index.php?import=blogger&noheader=true&authors=1',{blog:this.blog});
       
   310 				},
       
   311 				init: function() {
       
   312 					this.update();
       
   313 					var i = this.blog;
       
   314 					jQuery(this.button).bind('click', function(){return blogs[i].click();});
       
   315 					this.kicks = 0;
       
   316 					this.checks = 0;
       
   317 				},
       
   318 				click: function() {
       
   319 					if ( this.mode == 'init' || this.mode == 'stopped' || this.mode == 'posts' || this.mode == 'comments' ) {
       
   320 						this.mode = 'started';
       
   321 						this.start();
       
   322 						jQuery(this.button).attr('value', strings.stop);
       
   323 					} else if ( this.mode == 'started' ) {
       
   324 						return false; // let it run...
       
   325 						this.mode = 'stopped';
       
   326 						this.stop();
       
   327 						if ( this.checks > 0 || this.kicks > 0 ) {
       
   328 							this.mode = 'stopping';
       
   329 							jQuery(this.button).attr('value', strings.stopping);
       
   330 						} else {
       
   331 							jQuery(this.button).attr('value', strings.cont);
       
   332 						}
       
   333 					} else if ( this.mode == 'authors' ) {
       
   334 						document.location = 'index.php?import=blogger&authors=1&blog='+this.blog;
       
   335 						//this.mode = 'authors2';
       
   336 						//this.getauthors();
       
   337 					}
       
   338 					return false;
       
   339 				}
       
   340 			};
       
   341 			$init
       
   342 			jQuery.each(blogs, function(i, me){me.init();});
       
   343 		/* ]]> */
       
   344 		</script>\n";
       
   345 	}
       
   346 
       
   347 	// Handy function for stopping the script after a number of seconds.
       
   348 	function have_time() {
       
   349 		global $importer_started;
       
   350 		if ( time() - $importer_started > MAX_EXECUTION_TIME )
       
   351 			die('continue');
       
   352 		return true;
       
   353 	}
       
   354 
       
   355 	function get_total_results($type, $host) {
       
   356 		$headers = array(
       
   357 			"GET /feeds/$type/default?max-results=1&start-index=2 HTTP/1.0",
       
   358 			"Host: $host",
       
   359 			"Authorization: AuthSub token=\"$this->token\""
       
   360 		);
       
   361 		$request = join( "\r\n", $headers ) . "\r\n\r\n";
       
   362 		$sock = $this->_get_blogger_sock( $host );
       
   363 		if ( ! $sock ) return;
       
   364 		$response = $this->_txrx( $sock, $request );
       
   365 		$response = $this->parse_response( $response );
       
   366 		$parser = xml_parser_create();
       
   367 		xml_parse_into_struct($parser, $response['body'], $struct, $index);
       
   368 		xml_parser_free($parser);
       
   369 		$total_results = $struct[$index['OPENSEARCH:TOTALRESULTS'][0]]['value'];
       
   370 		return (int) $total_results;
       
   371 	}
       
   372 
       
   373 	function import_blog($blogID) {
       
   374 		global $importing_blog;
       
   375 		$importing_blog = $blogID;
       
   376 
       
   377 		if ( isset($_GET['authors']) )
       
   378 			return print($this->get_author_form());
       
   379 
       
   380 		header('Content-Type: text/plain');
       
   381 
       
   382 		if ( isset($_GET['status']) )
       
   383 			die($this->get_js_status());
       
   384 
       
   385 		if ( isset($_GET['saveauthors']) )
       
   386 			die($this->save_authors());
       
   387 
       
   388 		$blog = $this->blogs[$blogID];
       
   389 		$total_results = $this->get_total_results('posts', $blog['host']);
       
   390 		$this->blogs[$importing_blog]['total_posts'] = $total_results;
       
   391 
       
   392 		$start_index = $total_results - MAX_RESULTS + 1;
       
   393 
       
   394 		if ( isset( $this->blogs[$importing_blog]['posts_start_index'] ) )
       
   395 			$start_index = (int) $this->blogs[$importing_blog]['posts_start_index'];
       
   396 		elseif ( $total_results > MAX_RESULTS )
       
   397 			$start_index = $total_results - MAX_RESULTS + 1;
       
   398 		else
       
   399 			$start_index = 1;
       
   400 
       
   401 		// This will be positive until we have finished importing posts
       
   402 		if ( $start_index > 0 ) {
       
   403 			// Grab all the posts
       
   404 			$this->blogs[$importing_blog]['mode'] = 'posts';
       
   405 			$query = "start-index=$start_index&max-results=" . MAX_RESULTS;
       
   406 			do {
       
   407 				$index = $struct = $entries = array();
       
   408 				$headers = array(
       
   409 					"GET /feeds/posts/default?$query HTTP/1.0",
       
   410 					"Host: {$blog['host']}",
       
   411 					"Authorization: AuthSub token=\"$this->token\""
       
   412 				);
       
   413 				$request = join( "\r\n", $headers ) . "\r\n\r\n";
       
   414 				$sock = $this->_get_blogger_sock( $blog['host'] );
       
   415 				if ( ! $sock ) return; // TODO: Error handling
       
   416 				$response = $this->_txrx( $sock, $request );
       
   417 
       
   418 				$response = $this->parse_response( $response );
       
   419 
       
   420 				// Extract the entries and send for insertion
       
   421 				preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches );
       
   422 				if ( count( $matches[0] ) ) {
       
   423 					$entries = array_reverse($matches[0]);
       
   424 					foreach ( $entries as $entry ) {
       
   425 						$entry = "<feed>$entry</feed>";
       
   426 						$AtomParser = new AtomParser();
       
   427 						$AtomParser->parse( $entry );
       
   428 						$result = $this->import_post($AtomParser->entry);
       
   429 						if ( is_wp_error( $result ) )
       
   430 							return $result;
       
   431 						unset($AtomParser);
       
   432 					}
       
   433 				} else break;
       
   434 
       
   435 				// Get the 'previous' query string which we'll use on the next iteration
       
   436 				$query = '';
       
   437 				$links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches);
       
   438 				if ( count( $matches[1] ) )
       
   439 					foreach ( $matches[1] as $match )
       
   440 						if ( preg_match('/rel=.previous./', $match) )
       
   441 							$query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') );
       
   442 
       
   443 				if ( $query ) {
       
   444 					parse_str($query, $q);
       
   445 					$this->blogs[$importing_blog]['posts_start_index'] = (int) $q['start-index'];
       
   446 				} else
       
   447 					$this->blogs[$importing_blog]['posts_start_index'] = 0;
       
   448 				$this->save_vars();
       
   449 			} while ( !empty( $query ) && $this->have_time() );
       
   450 		}
       
   451 
       
   452 		$total_results = $this->get_total_results( 'comments', $blog['host'] );
       
   453 		$this->blogs[$importing_blog]['total_comments'] = $total_results;
       
   454 
       
   455 		if ( isset( $this->blogs[$importing_blog]['comments_start_index'] ) )
       
   456 			$start_index = (int) $this->blogs[$importing_blog]['comments_start_index'];
       
   457 		elseif ( $total_results > MAX_RESULTS )
       
   458 			$start_index = $total_results - MAX_RESULTS + 1;
       
   459 		else
       
   460 			$start_index = 1;
       
   461 
       
   462 		if ( $start_index > 0 ) {
       
   463 			// Grab all the comments
       
   464 			$this->blogs[$importing_blog]['mode'] = 'comments';
       
   465 			$query = "start-index=$start_index&max-results=" . MAX_RESULTS;
       
   466 			do {
       
   467 				$index = $struct = $entries = array();
       
   468 				$headers = array(
       
   469 					"GET /feeds/comments/default?$query HTTP/1.0",
       
   470 					"Host: {$blog['host']}",
       
   471 					"Authorization: AuthSub token=\"$this->token\""
       
   472 				);
       
   473 				$request = join( "\r\n", $headers ) . "\r\n\r\n";
       
   474 				$sock = $this->_get_blogger_sock( $blog['host'] );
       
   475 				if ( ! $sock ) return; // TODO: Error handling
       
   476 				$response = $this->_txrx( $sock, $request );
       
   477 
       
   478 				$response = $this->parse_response( $response );
       
   479 
       
   480 				// Extract the comments and send for insertion
       
   481 				preg_match_all( '/<entry[^>]*>.*?<\/entry>/s', $response['body'], $matches );
       
   482 				if ( count( $matches[0] ) ) {
       
   483 					$entries = array_reverse( $matches[0] );
       
   484 					foreach ( $entries as $entry ) {
       
   485 						$entry = "<feed>$entry</feed>";
       
   486 						$AtomParser = new AtomParser();
       
   487 						$AtomParser->parse( $entry );
       
   488 						$this->import_comment($AtomParser->entry);
       
   489 						unset($AtomParser);
       
   490 					}
       
   491 				}
       
   492 
       
   493 				// Get the 'previous' query string which we'll use on the next iteration
       
   494 				$query = '';
       
   495 				$links = preg_match_all('/<link([^>]*)>/', $response['body'], $matches);
       
   496 				if ( count( $matches[1] ) )
       
   497 					foreach ( $matches[1] as $match )
       
   498 						if ( preg_match('/rel=.previous./', $match) )
       
   499 							$query = @html_entity_decode( preg_replace('/^.*href=[\'"].*\?(.+)[\'"].*$/', '$1', $match), ENT_COMPAT, get_option('blog_charset') );
       
   500 
       
   501 				parse_str($query, $q);
       
   502 
       
   503 				$this->blogs[$importing_blog]['comments_start_index'] = (int) $q['start-index'];
       
   504 				$this->save_vars();
       
   505 			} while ( !empty( $query ) && $this->have_time() );
       
   506 		}
       
   507 		$this->blogs[$importing_blog]['mode'] = 'authors';
       
   508 		$this->save_vars();
       
   509 		if ( !$this->blogs[$importing_blog]['posts_done'] && !$this->blogs[$importing_blog]['comments_done'] )
       
   510 			die('nothing');
       
   511 		do_action('import_done', 'blogger');
       
   512 		die('done');
       
   513 	}
       
   514 
       
   515 	function convert_date( $date ) {
       
   516 	    preg_match('#([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.[0-9]+)?(Z|[\+|\-][0-9]{2,4}){0,1}#', $date, $date_bits);
       
   517 	    $offset = iso8601_timezone_to_offset( $date_bits[7] );
       
   518 		$timestamp = gmmktime($date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1]);
       
   519 		$timestamp -= $offset; // Convert from Blogger local time to GMT
       
   520 		$timestamp += get_option('gmt_offset') * 3600; // Convert from GMT to WP local time
       
   521 		return gmdate('Y-m-d H:i:s', $timestamp);
       
   522 	}
       
   523 
       
   524 	function no_apos( $string ) {
       
   525 		return str_replace( '&apos;', "'", $string);
       
   526 	}
       
   527 
       
   528 	function min_whitespace( $string ) {
       
   529 		return preg_replace( '|\s+|', ' ', $string );
       
   530 	}
       
   531 
       
   532 	function import_post( $entry ) {
       
   533 		global $importing_blog;
       
   534 
       
   535 		// The old permalink is all Blogger gives us to link comments to their posts.
       
   536 		if ( isset( $entry->draft ) )
       
   537 			$rel = 'self';
       
   538 		else
       
   539 			$rel = 'alternate';
       
   540 		foreach ( $entry->links as $link ) {
       
   541 			if ( $link['rel'] == $rel ) {
       
   542 				$parts = parse_url( $link['href'] );
       
   543 				$entry->old_permalink = $parts['path'];
       
   544 				break;
       
   545 			}
       
   546 		}
       
   547 
       
   548 		$post_date    = $this->convert_date( $entry->published );
       
   549 		$post_content = trim( addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) ) );
       
   550 		$post_title   = trim( addslashes( $this->no_apos( $this->min_whitespace( $entry->title ) ) ) );
       
   551 		$post_status  = isset( $entry->draft ) ? 'draft' : 'publish';
       
   552 
       
   553 		// Clean up content
       
   554 		$post_content = preg_replace_callback('|<(/?[A-Z]+)|', create_function('$match', 'return "<" . strtolower($match[1]);'), $post_content);
       
   555 		$post_content = str_replace('<br>', '<br />', $post_content);
       
   556 		$post_content = str_replace('<hr>', '<hr />', $post_content);
       
   557 
       
   558 		// Checks for duplicates
       
   559 		if ( isset( $this->blogs[$importing_blog]['posts'][$entry->old_permalink] ) ) {
       
   560 			++$this->blogs[$importing_blog]['posts_skipped'];
       
   561 		} elseif ( $post_id = post_exists( $post_title, $post_content, $post_date ) ) {
       
   562 			$this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
       
   563 			++$this->blogs[$importing_blog]['posts_skipped'];
       
   564 		} else {
       
   565 			$post = compact('post_date', 'post_content', 'post_title', 'post_status');
       
   566 
       
   567 			$post_id = wp_insert_post($post);
       
   568 			if ( is_wp_error( $post_id ) )
       
   569 				return $post_id;
       
   570 
       
   571 			wp_create_categories( array_map( 'addslashes', $entry->categories ), $post_id );
       
   572 
       
   573 			$author = $this->no_apos( strip_tags( $entry->author ) );
       
   574 
       
   575 			add_post_meta( $post_id, 'blogger_blog', $this->blogs[$importing_blog]['host'], true );
       
   576 			add_post_meta( $post_id, 'blogger_author', $author, true );
       
   577 			add_post_meta( $post_id, 'blogger_permalink', $entry->old_permalink, true );
       
   578 
       
   579 			$this->blogs[$importing_blog]['posts'][$entry->old_permalink] = $post_id;
       
   580 			++$this->blogs[$importing_blog]['posts_done'];
       
   581 		}
       
   582 		$this->save_vars();
       
   583 		return;
       
   584 	}
       
   585 
       
   586 	function import_comment( $entry ) {
       
   587 		global $importing_blog;
       
   588 
       
   589 		// Drop the #fragment and we have the comment's old post permalink.
       
   590 		foreach ( $entry->links as $link ) {
       
   591 			if ( $link['rel'] == 'alternate' ) {
       
   592 				$parts = parse_url( $link['href'] );
       
   593 				$entry->old_permalink = $parts['fragment'];
       
   594 				$entry->old_post_permalink = $parts['path'];
       
   595 				break;
       
   596 			}
       
   597 		}
       
   598 
       
   599 		$comment_post_ID = (int) $this->blogs[$importing_blog]['posts'][$entry->old_post_permalink];
       
   600 		preg_match('#<name>(.+?)</name>.*(?:\<uri>(.+?)</uri>)?#', $entry->author, $matches);
       
   601 		$comment_author  = addslashes( $this->no_apos( strip_tags( (string) $matches[1] ) ) );
       
   602 		$comment_author_url = addslashes( $this->no_apos( strip_tags( (string) $matches[2] ) ) );
       
   603 		$comment_date    = $this->convert_date( $entry->updated );
       
   604 		$comment_content = addslashes( $this->no_apos( @html_entity_decode( $entry->content, ENT_COMPAT, get_option('blog_charset') ) ) );
       
   605 
       
   606 		// Clean up content
       
   607 		$comment_content = preg_replace_callback('|<(/?[A-Z]+)|', create_function('$match', 'return "<" . strtolower($match[1]);'), $comment_content);
       
   608 		$comment_content = str_replace('<br>', '<br />', $comment_content);
       
   609 		$comment_content = str_replace('<hr>', '<hr />', $comment_content);
       
   610 
       
   611 		// Checks for duplicates
       
   612 		if (
       
   613 			isset( $this->blogs[$importing_blog]['comments'][$entry->old_permalink] ) ||
       
   614 			comment_exists( $comment_author, $comment_date )
       
   615 		) {
       
   616 			++$this->blogs[$importing_blog]['comments_skipped'];
       
   617 		} else {
       
   618 			$comment = compact('comment_post_ID', 'comment_author', 'comment_author_url', 'comment_date', 'comment_content');
       
   619 
       
   620 			$comment_id = wp_insert_comment($comment);
       
   621 
       
   622 			$this->blogs[$importing_blog]['comments'][$entry->old_permalink] = $comment_id;
       
   623 
       
   624 			++$this->blogs[$importing_blog]['comments_done'];
       
   625 		}
       
   626 		$this->save_vars();
       
   627 	}
       
   628 
       
   629 	function get_js_status($blog = false) {
       
   630 		global $importing_blog;
       
   631 		if ( $blog === false )
       
   632 			$blog = $this->blogs[$importing_blog];
       
   633 		else
       
   634 			$blog = $this->blogs[$blog];
       
   635 		$p1 = isset( $blog['posts_done'] ) ? (int) $blog['posts_done'] : 0;
       
   636 		$p2 = isset( $blog['total_posts'] ) ? (int) $blog['total_posts'] : 0;
       
   637 		$c1 = isset( $blog['comments_done'] ) ? (int) $blog['comments_done'] : 0;
       
   638 		$c2 = isset( $blog['total_comments'] ) ? (int) $blog['total_comments'] : 0;
       
   639 		return "{p1:$p1,p2:$p2,c1:$c1,c2:$c2}";
       
   640 	}
       
   641 
       
   642 	function get_author_form($blog = false) {
       
   643 		global $importing_blog, $wpdb, $current_user;
       
   644 		if ( $blog === false )
       
   645 			$blog = & $this->blogs[$importing_blog];
       
   646 		else
       
   647 			$blog = & $this->blogs[$blog];
       
   648 
       
   649 		if ( !isset( $blog['authors'] ) ) {
       
   650 			$post_ids = array_values($blog['posts']);
       
   651 			$authors = (array) $wpdb->get_col("SELECT DISTINCT meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN (" . join( ',', $post_ids ) . ")");
       
   652 			$blog['authors'] = array_map(null, $authors, array_fill(0, count($authors), $current_user->ID));
       
   653 			$this->save_vars();
       
   654 		}
       
   655 
       
   656 		$directions = __('All posts were imported with the current user as author. Use this form to move each Blogger user&#8217;s posts to a different WordPress user. You may <a href="users.php">add users</a> and then return to this page and complete the user mapping. This form may be used as many times as you like until you activate the &#8220;Restart&#8221; function below.');
       
   657 		$heading = __('Author mapping');
       
   658 		$blogtitle = "{$blog['title']} ({$blog['host']})";
       
   659 		$mapthis = __('Blogger username');
       
   660 		$tothis = __('WordPress login');
       
   661 		$submit = esc_js( __('Save Changes') );
       
   662 
       
   663 		foreach ( $blog['authors'] as $i => $author )
       
   664 			$rows .= "<tr><td><label for='authors[$i]'>{$author[0]}</label></td><td><select name='authors[$i]' id='authors[$i]'>" . $this->get_user_options($author[1]) . "</select></td></tr>";
       
   665 
       
   666 		return "<div class='wrap'><h2>$heading</h2><h3>$blogtitle</h3><p>$directions</p><form action='index.php?import=blogger&amp;noheader=true&saveauthors=1' method='post'><input type='hidden' name='blog' value='" . esc_attr($importing_blog) . "' /><table cellpadding='5'><thead><td>$mapthis</td><td>$tothis</td></thead>$rows<tr><td></td><td class='submit'><input type='submit' class='button authorsubmit' value='$submit' /></td></tr></table></form></div>";
       
   667 	}
       
   668 
       
   669 	function get_user_options($current) {
       
   670 		global $importer_users;
       
   671 		if ( ! isset( $importer_users ) )
       
   672 			$importer_users = (array) get_users_of_blog();
       
   673 
       
   674 		foreach ( $importer_users as $user ) {
       
   675 			$sel = ( $user->user_id == $current ) ? " selected='selected'" : '';
       
   676 			$options .= "<option value='$user->user_id'$sel>$user->display_name</option>";
       
   677 		}
       
   678 
       
   679 		return $options;
       
   680 	}
       
   681 
       
   682 	function save_authors() {
       
   683 		global $importing_blog, $wpdb;
       
   684 		$authors = (array) $_POST['authors'];
       
   685 
       
   686 		$host = $this->blogs[$importing_blog]['host'];
       
   687 
       
   688 		// Get an array of posts => authors
       
   689 		$post_ids = (array) $wpdb->get_col( $wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'blogger_blog' AND meta_value = %s", $host) );
       
   690 		$post_ids = join( ',', $post_ids );
       
   691 		$results = (array) $wpdb->get_results("SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = 'blogger_author' AND post_id IN ($post_ids)");
       
   692 		foreach ( $results as $row )
       
   693 			$authors_posts[$row->post_id] = $row->meta_value;
       
   694 
       
   695 		foreach ( $authors as $author => $user_id ) {
       
   696 			$user_id = (int) $user_id;
       
   697 
       
   698 			// Skip authors that haven't been changed
       
   699 			if ( $user_id == $this->blogs[$importing_blog]['authors'][$author][1] )
       
   700 				continue;
       
   701 
       
   702 			// Get a list of the selected author's posts
       
   703 			$post_ids = (array) array_keys( $authors_posts, $this->blogs[$importing_blog]['authors'][$author][0] );
       
   704 			$post_ids = join( ',', $post_ids);
       
   705 
       
   706 			$wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET post_author = %d WHERE id IN ($post_ids)", $user_id) );
       
   707 			$this->blogs[$importing_blog]['authors'][$author][1] = $user_id;
       
   708 		}
       
   709 		$this->save_vars();
       
   710 
       
   711 		wp_redirect('edit.php');
       
   712 	}
       
   713 
       
   714 	function _get_auth_sock() {
       
   715 		// Connect to https://www.google.com
       
   716 		if ( !$sock = @ fsockopen('ssl://www.google.com', 443, $errno, $errstr) ) {
       
   717 			$this->uh_oh(
       
   718 				__('Could not connect to https://www.google.com'),
       
   719 				__('There was a problem opening a secure connection to Google. This is what went wrong:'),
       
   720 				"$errstr ($errno)"
       
   721 			);
       
   722 			return false;
       
   723 		}
       
   724 		return $sock;
       
   725 	}
       
   726 
       
   727 	function _get_blogger_sock($host = 'www2.blogger.com') {
       
   728 		if ( !$sock = @ fsockopen($host, 80, $errno, $errstr) ) {
       
   729 			$this->uh_oh(
       
   730 				sprintf( __('Could not connect to %s'), $host ),
       
   731 				__('There was a problem opening a connection to Blogger. This is what went wrong:'),
       
   732 				"$errstr ($errno)"
       
   733 			);
       
   734 			return false;
       
   735 		}
       
   736 		return $sock;
       
   737 	}
       
   738 
       
   739 	function _txrx( $sock, $request ) {
       
   740 		fwrite( $sock, $request );
       
   741 		while ( ! feof( $sock ) )
       
   742 			$response .= @ fread ( $sock, 8192 );
       
   743 		fclose( $sock );
       
   744 		return $response;
       
   745 	}
       
   746 
       
   747 	function revoke($token) {
       
   748 		$headers = array(
       
   749 			"GET /accounts/AuthSubRevokeToken HTTP/1.0",
       
   750 			"Authorization: AuthSub token=\"$token\""
       
   751 		);
       
   752 		$request = join( "\r\n", $headers ) . "\r\n\r\n";
       
   753 		$sock = $this->_get_auth_sock( );
       
   754 		if ( ! $sock ) return false;
       
   755 		$this->_txrx( $sock, $request );
       
   756 	}
       
   757 
       
   758 	function restart() {
       
   759 		global $wpdb;
       
   760 		$options = get_option( 'blogger_importer' );
       
   761 
       
   762 		if ( isset( $options['token'] ) )
       
   763 			$this->revoke( $options['token'] );
       
   764 
       
   765 		delete_option('blogger_importer');
       
   766 		$wpdb->query("DELETE FROM $wpdb->postmeta WHERE meta_key = 'blogger_author'");
       
   767 		wp_redirect('?import=blogger');
       
   768 	}
       
   769 
       
   770 	// Returns associative array of code, header, cookies, body. Based on code from php.net.
       
   771 	function parse_response($this_response) {
       
   772 		// Split response into header and body sections
       
   773 		list($response_headers, $response_body) = explode("\r\n\r\n", $this_response, 2);
       
   774 		$response_header_lines = explode("\r\n", $response_headers);
       
   775 
       
   776 		// First line of headers is the HTTP response code
       
   777 		$http_response_line = array_shift($response_header_lines);
       
   778 		if(preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@',$http_response_line, $matches)) { $response_code = $matches[1]; }
       
   779 
       
   780 		// put the rest of the headers in an array
       
   781 		$response_header_array = array();
       
   782 		foreach($response_header_lines as $header_line) {
       
   783 			list($header,$value) = explode(': ', $header_line, 2);
       
   784 			$response_header_array[$header] .= $value."\n";
       
   785 		}
       
   786 
       
   787 		$cookie_array = array();
       
   788 		$cookies = explode("\n", $response_header_array["Set-Cookie"]);
       
   789 		foreach($cookies as $this_cookie) { array_push($cookie_array, "Cookie: ".$this_cookie); }
       
   790 
       
   791 		return array("code" => $response_code, "header" => $response_header_array, "cookies" => $cookie_array, "body" => $response_body);
       
   792 	}
       
   793 
       
   794 	// Step 9: Congratulate the user
       
   795 	function congrats() {
       
   796 		$blog = (int) $_GET['blog'];
       
   797 		echo '<h1>'.__('Congratulations!').'</h1><p>'.__('Now that you have imported your Blogger blog into WordPress, what are you going to do? Here are some suggestions:').'</p><ul><li>'.__('That was hard work! Take a break.').'</li>';
       
   798 		if ( count($this->import['blogs']) > 1 )
       
   799 			echo '<li>'.__('In case you haven&#8217;t done it already, you can import the posts from your other blogs:'). $this->show_blogs() . '</li>';
       
   800 		if ( $n = count($this->import['blogs'][$blog]['newusers']) )
       
   801 			echo '<li>'.sprintf(__('Go to <a href="%s" target="%s">Authors &amp; Users</a>, where you can modify the new user(s) or delete them. If you want to make all of the imported posts yours, you will be given that option when you delete the new authors.'), 'users.php', '_parent').'</li>';
       
   802 		echo '<li>'.__('For security, click the link below to reset this importer.').'</li>';
       
   803 		echo '</ul>';
       
   804 	}
       
   805 
       
   806 	// Figures out what to do, then does it.
       
   807 	function start() {
       
   808 		if ( isset($_POST['restart']) )
       
   809 			$this->restart();
       
   810 
       
   811 		$options = get_option('blogger_importer');
       
   812 
       
   813 		if ( is_array($options) )
       
   814 			foreach ( $options as $key => $value )
       
   815 				$this->$key = $value;
       
   816 
       
   817 		if ( isset( $_REQUEST['blog'] ) ) {
       
   818 			$blog = is_array($_REQUEST['blog']) ? array_shift( $keys = array_keys( $_REQUEST['blog'] ) ) : $_REQUEST['blog'];
       
   819 			$blog = (int) $blog;
       
   820 			$result = $this->import_blog( $blog );
       
   821 			if ( is_wp_error( $result ) )
       
   822 				echo $result->get_error_message();
       
   823 		} elseif ( isset($_GET['token']) )
       
   824 			$this->auth();
       
   825 		elseif ( isset($this->token) && $this->token_is_valid() )
       
   826 			$this->show_blogs();
       
   827 		else
       
   828 			$this->greet();
       
   829 
       
   830 		$saved = $this->save_vars();
       
   831 
       
   832 		if ( $saved && !isset($_GET['noheader']) ) {
       
   833 			$restart = __('Restart');
       
   834 			$message = __('We have saved some information about your Blogger account in your WordPress database. Clearing this information will allow you to start over. Restarting will not affect any posts you have already imported. If you attempt to re-import a blog, duplicate posts and comments will be skipped.');
       
   835 			$submit = esc_attr__('Clear account information');
       
   836 			echo "<div class='wrap'><h2>$restart</h2><p>$message</p><form method='post' action='?import=blogger&amp;noheader=true'><p class='submit' style='text-align:left;'><input type='submit' class='button' value='$submit' name='restart' /></p></form></div>";
       
   837 		}
       
   838 	}
       
   839 
       
   840 	function save_vars() {
       
   841 		$vars = get_object_vars($this);
       
   842 		update_option( 'blogger_importer', $vars );
       
   843 
       
   844 		return !empty($vars);
       
   845 	}
       
   846 
       
   847 	function admin_head() {
       
   848 ?>
       
   849 <style type="text/css">
       
   850 td { text-align: center; line-height: 2em;}
       
   851 thead td { font-weight: bold; }
       
   852 .bar {
       
   853 	width: 200px;
       
   854 	text-align: left;
       
   855 	line-height: 2em;
       
   856 	padding: 0px;
       
   857 }
       
   858 .ind {
       
   859 	position: absolute;
       
   860 	background-color: #83B4D8;
       
   861 	width: 1px;
       
   862 	z-index: 9;
       
   863 }
       
   864 .stat {
       
   865 	z-index: 10;
       
   866 	position: relative;
       
   867 	text-align: center;
       
   868 }
       
   869 </style>
       
   870 <?php
       
   871 	}
       
   872 
       
   873 	function Blogger_Import() {
       
   874 		global $importer_started;
       
   875 		$importer_started = time();
       
   876 		if ( isset( $_GET['import'] ) && $_GET['import'] == 'blogger' ) {
       
   877 			wp_enqueue_script('jquery');
       
   878 			add_action('admin_head', array(&$this, 'admin_head'));
       
   879 		}
       
   880 	}
       
   881 }
       
   882 
       
   883 $blogger_import = new Blogger_Import();
       
   884 
       
   885 register_importer('blogger', __('Blogger'), __('Import posts, comments, and users from a Blogger blog.'), array ($blogger_import, 'start'));
       
   886 
       
   887 class AtomEntry {
       
   888 	var $links = array();
       
   889 	var $categories = array();
       
   890 }
       
   891 
       
   892 class AtomParser {
       
   893 
       
   894 	var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights');
       
   895 	var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft','author');
       
   896 
       
   897 	var $depth = 0;
       
   898 	var $indent = 2;
       
   899 	var $in_content;
       
   900 	var $ns_contexts = array();
       
   901 	var $ns_decls = array();
       
   902 	var $is_xhtml = false;
       
   903 	var $skipped_div = false;
       
   904 
       
   905 	var $entry;
       
   906 
       
   907 	function AtomParser() {
       
   908 
       
   909 		$this->entry = new AtomEntry();
       
   910 		$this->map_attrs_func = create_function('$k,$v', 'return "$k=\"$v\"";');
       
   911 		$this->map_xmlns_func = create_function('$p,$n', '$xd = "xmlns"; if(strlen($n[0])>0) $xd .= ":{$n[0]}"; return "{$xd}=\"{$n[1]}\"";');
       
   912 	}
       
   913 
       
   914 	function parse($xml) {
       
   915 
       
   916 		global $app_logging;
       
   917 		array_unshift($this->ns_contexts, array());
       
   918 
       
   919 		$parser = xml_parser_create_ns();
       
   920 		xml_set_object($parser, $this);
       
   921 		xml_set_element_handler($parser, "start_element", "end_element");
       
   922 		xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);
       
   923 		xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0);
       
   924 		xml_set_character_data_handler($parser, "cdata");
       
   925 		xml_set_default_handler($parser, "_default");
       
   926 		xml_set_start_namespace_decl_handler($parser, "start_ns");
       
   927 		xml_set_end_namespace_decl_handler($parser, "end_ns");
       
   928 
       
   929 		$contents = "";
       
   930 
       
   931 		xml_parse($parser, $xml);
       
   932 
       
   933 		xml_parser_free($parser);
       
   934 
       
   935 		return true;
       
   936 	}
       
   937 
       
   938 	function start_element($parser, $name, $attrs) {
       
   939 
       
   940 		$tag = array_pop(split(":", $name));
       
   941 
       
   942 		array_unshift($this->ns_contexts, $this->ns_decls);
       
   943 
       
   944 		$this->depth++;
       
   945 
       
   946 		if(!empty($this->in_content)) {
       
   947 			$attrs_prefix = array();
       
   948 
       
   949 			// resolve prefixes for attributes
       
   950 			foreach($attrs as $key => $value) {
       
   951 				$attrs_prefix[$this->ns_to_prefix($key)] = $this->xml_escape($value);
       
   952 			}
       
   953 			$attrs_str = join(' ', array_map($this->map_attrs_func, array_keys($attrs_prefix), array_values($attrs_prefix)));
       
   954 			if(strlen($attrs_str) > 0) {
       
   955 				$attrs_str = " " . $attrs_str;
       
   956 			}
       
   957 
       
   958 			$xmlns_str = join(' ', array_map($this->map_xmlns_func, array_keys($this->ns_contexts[0]), array_values($this->ns_contexts[0])));
       
   959 			if(strlen($xmlns_str) > 0) {
       
   960 				$xmlns_str = " " . $xmlns_str;
       
   961 			}
       
   962 
       
   963 			// handle self-closing tags (case: a new child found right-away, no text node)
       
   964 			if(count($this->in_content) == 2) {
       
   965 				array_push($this->in_content, ">");
       
   966 			}
       
   967 
       
   968 			array_push($this->in_content, "<". $this->ns_to_prefix($name) ."{$xmlns_str}{$attrs_str}");
       
   969 		} else if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) {
       
   970 			$this->in_content = array();
       
   971 			$this->is_xhtml = $attrs['type'] == 'xhtml';
       
   972 			array_push($this->in_content, array($tag,$this->depth));
       
   973 		} else if($tag == 'link') {
       
   974 			array_push($this->entry->links, $attrs);
       
   975 		} else if($tag == 'category') {
       
   976 			array_push($this->entry->categories, $attrs['term']);
       
   977 		}
       
   978 
       
   979 		$this->ns_decls = array();
       
   980 	}
       
   981 
       
   982 	function end_element($parser, $name) {
       
   983 
       
   984 		$tag = array_pop(split(":", $name));
       
   985 
       
   986 		if(!empty($this->in_content)) {
       
   987 			if($this->in_content[0][0] == $tag &&
       
   988 			$this->in_content[0][1] == $this->depth) {
       
   989 				array_shift($this->in_content);
       
   990 				if($this->is_xhtml) {
       
   991 					$this->in_content = array_slice($this->in_content, 2, count($this->in_content)-3);
       
   992 				}
       
   993 				$this->entry->$tag = join('',$this->in_content);
       
   994 				$this->in_content = array();
       
   995 			} else {
       
   996 				$endtag = $this->ns_to_prefix($name);
       
   997 				if (strpos($this->in_content[count($this->in_content)-1], '<' . $endtag) !== false) {
       
   998 					array_push($this->in_content, "/>");
       
   999 				} else {
       
  1000 					array_push($this->in_content, "</$endtag>");
       
  1001 				}
       
  1002 			}
       
  1003 		}
       
  1004 
       
  1005 		array_shift($this->ns_contexts);
       
  1006 
       
  1007 		#print str_repeat(" ", $this->depth * $this->indent) . "end_element('$name')" ."\n";
       
  1008 
       
  1009 		$this->depth--;
       
  1010 	}
       
  1011 
       
  1012 	function start_ns($parser, $prefix, $uri) {
       
  1013 		#print str_repeat(" ", $this->depth * $this->indent) . "starting: " . $prefix . ":" . $uri . "\n";
       
  1014 		array_push($this->ns_decls, array($prefix,$uri));
       
  1015 	}
       
  1016 
       
  1017 	function end_ns($parser, $prefix) {
       
  1018 		#print str_repeat(" ", $this->depth * $this->indent) . "ending: #" . $prefix . "#\n";
       
  1019 	}
       
  1020 
       
  1021 	function cdata($parser, $data) {
       
  1022 		#print str_repeat(" ", $this->depth * $this->indent) . "data: #" . $data . "#\n";
       
  1023 		if(!empty($this->in_content)) {
       
  1024 			// handle self-closing tags (case: text node found, need to close element started)
       
  1025 			if (strpos($this->in_content[count($this->in_content)-1], '<') !== false) {
       
  1026 				array_push($this->in_content, ">");
       
  1027 			}
       
  1028 			array_push($this->in_content, $this->xml_escape($data));
       
  1029 		}
       
  1030 	}
       
  1031 
       
  1032 	function _default($parser, $data) {
       
  1033 		# when does this gets called?
       
  1034 	}
       
  1035 
       
  1036 
       
  1037 	function ns_to_prefix($qname) {
       
  1038 		$components = split(":", $qname);
       
  1039 		$name = array_pop($components);
       
  1040 
       
  1041 		if(!empty($components)) {
       
  1042 			$ns = join(":",$components);
       
  1043 			foreach($this->ns_contexts as $context) {
       
  1044 				foreach($context as $mapping) {
       
  1045 					if($mapping[1] == $ns && strlen($mapping[0]) > 0) {
       
  1046 						return "$mapping[0]:$name";
       
  1047 					}
       
  1048 				}
       
  1049 			}
       
  1050 		}
       
  1051 		return $name;
       
  1052 	}
       
  1053 
       
  1054 	function xml_escape($string)
       
  1055 	{
       
  1056 			 return str_replace(array('&','"',"'",'<','>'),
       
  1057 				array('&amp;','&quot;','&apos;','&lt;','&gt;'),
       
  1058 				$string );
       
  1059 	}
       
  1060 }
       
  1061 
       
  1062 ?>