Make WordPress Core

Ticket #15197: 15197-import.diff

File 15197-import.diff, 75.2 KB (added by duck_, 15 years ago)

Import patch against wordpress-importer plugin checkout

  • trunk/parsers.php

     
     1<?php
     2/**
     3 * WordPress eXtended RSS file parser implementations
     4 *
     5 * @package WordPress
     6 * @subpackage Importer
     7 */
     8
     9/**
     10 * WordPress Importer class for managing parsing of WXR files
     11 */
     12class WXR_Parser {
     13        function parse( $file ) {
     14                if ( extension_loaded( 'simplexml' ) )
     15                        $parser = new WXR_Parser_SimpleXML;
     16                else if ( extension_loaded( 'xml' ) )
     17                        $parser = new WXR_Parser_XML;
     18                else
     19                        $parser = new WXR_Parser_Regex;
     20       
     21                return $parser->parse( $file );
     22        }
     23}
     24
     25/**
     26 * WXR Parser that makes use of the SimpleXML PHP extension
     27 */
     28class WXR_Parser_SimpleXML {
     29        function parse( $file ) {
     30                $authors = $posts = $categories = $tags = $terms = array();
     31               
     32                $internal_errors = libxml_use_internal_errors(true);
     33                $xml = simplexml_load_file( $file );
     34                // halt if loading produces an error
     35                if ( ! $xml )
     36                        return new WP_Error( 'WXR_parse_error', __( 'There was an error when reading this WXR file', 'wordpress-importer' ) );
     37
     38                $wxr_version = $xml->xpath('/rss/channel/wp:wxr_version');
     39                if ( ! $wxr_version )
     40                        return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) );
     41
     42                $wxr_version = (string) trim( $wxr_version[0] );
     43                // confirm that we are dealing with the correct file format
     44                if ( ! preg_match( '/^\d\.\d$/', $wxr_version ) )
     45                        return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) );
     46                       
     47                $base_url = $xml->xpath('/rss/channel/wp:base_site_url');
     48                $base_url = (string) trim( $base_url[0] );
     49               
     50                $namespaces = $xml->getDocNamespaces();
     51                if ( ! isset( $namespaces['wp'] ) )
     52                        $namespaces['wp'] = 'http://wordpress.org/export/1.1/';
     53                if ( ! isset( $namespaces['excerpt'] ) )
     54                        $namespaces['excerpt'] = 'http://wordpress.org/export/1.1/excerpt/';           
     55
     56                // grab authors
     57                foreach ( $xml->xpath('/rss/channel/wp:author') as $author_arr ) {
     58                        $a = $author_arr->children( $namespaces['wp'] );
     59                        $login = (string) $a->author_login;
     60                        $authors[$login] = array(
     61                                'author_login' => $login,
     62                                'author_email' => (string) $a->author_email,
     63                                'author_display_name' => (string) $a->author_display_name,
     64                                'author_first_name' => (string) $a->author_first_name,
     65                                'author_last_name' => (string) $a->author_last_name
     66                        );
     67                }
     68
     69                // grab cats, tags and terms
     70                foreach ( $xml->xpath('/rss/channel/wp:category') as $term_arr ) {
     71                        $t = $term_arr->children( $namespaces['wp'] );
     72                        $categories[] = array(
     73                                'term_id' => (int) $t->term_id,
     74                                'category_nicename' => (string) $t->category_nicename,
     75                                'category_parent' => (string) $t->category_parent,
     76                                'cat_name' => (string) $t->cat_name,
     77                                'category_description' => (string) $t->category_description
     78                        );
     79                }
     80
     81                foreach ( $xml->xpath('/rss/channel/wp:tag') as $term_arr ) {
     82                        $t = $term_arr->children( $namespaces['wp'] );
     83                        $tags[] = array(
     84                                'term_id' => (int) $t->term_id,
     85                                'tag_slug' => (string) $t->tag_slug,
     86                                'tag_name' => (string) $t->tag_name,
     87                                'tag_description' => (string) $t->tag_description
     88                        );
     89                }
     90
     91                foreach ( $xml->xpath('/rss/channel/wp:term') as $term_arr ) {
     92                        $t = $term_arr->children( $namespaces['wp'] );
     93                        $terms[] = array(
     94                                'term_id' => (int) $t->term_id,
     95                                'term_taxonomy' => (string) $t->term_taxonomy,
     96                                'slug' => (string) $t->term_slug,
     97                                'term_parent' => (string) $t->term_parent,
     98                                'term_name' => (string) $t->term_name,
     99                                'term_description' => (string) $t->term_description
     100                        );
     101                }
     102
     103                // grab posts
     104                foreach ( $xml->channel->item as $item ) {
     105                        $post = array(
     106                                'post_title' => (string) $item->title,
     107                                'guid' => (string) $item->guid,
     108                        );
     109       
     110                        $dc = $item->children( 'http://purl.org/dc/elements/1.1/' );
     111                        $post['post_author'] = (string) $dc->creator;
     112       
     113                        $content = $item->children( 'http://purl.org/rss/1.0/modules/content/' );
     114                        $excerpt = $item->children( $namespaces['excerpt'] );
     115                        $post['post_content'] = (string) $content->encoded;
     116                        $post['post_excerpt'] = (string) $excerpt->encoded;     
     117       
     118                        $wp = $item->children( $namespaces['wp'] );
     119                        $post['post_id'] = (int) $wp->post_id;
     120                        $post['post_date'] = (string) $wp->post_date;
     121                        $post['post_date_gmt'] = (string) $wp->post_date_gmt;
     122                        $post['comment_status'] = (string) $wp->comment_status;
     123                        $post['ping_status'] = (string) $wp->ping_status;
     124                        $post['post_name'] = (string) $wp->post_name;
     125                        $post['status'] = (string) $wp->status;
     126                        $post['post_parent'] = (int) $wp->post_parent;
     127                        $post['menu_order'] = (int) $wp->menu_order;
     128                        $post['post_type'] = (string) $wp->post_type;
     129                        $post['post_password'] = (string) $wp->post_password;
     130                        $post['is_sticky'] = (int) $wp->is_sticky;
     131       
     132                        foreach ( $item->category as $c ) {
     133                                $att = $c->attributes();
     134                                if ( isset( $att['nicename'] ) )
     135                                        $post['terms'][] = array(
     136                                                'name' => (string) $c,
     137                                                'slug' => (string) $att['nicename'],
     138                                                'domain' => (string) $att['domain']
     139                                        );
     140                        }       
     141       
     142                        foreach ( $wp->postmeta as $meta ) {
     143                                $post['postmeta'][] = array(
     144                                        'key' => (string) $meta->meta_key,
     145                                        'value' => (string) $meta->meta_value,
     146                                );
     147                        }
     148       
     149                        foreach ( $wp->comment as $comment ) {
     150                                $post['comments'][] = array(
     151                                        'comment_id' => (int) $comment->comment_id,
     152                                        'comment_author' => (string) $comment->comment_author,
     153                                        'comment_author_email' => (string) $comment->comment_author_email,
     154                                        'comment_author_IP' => (string) $comment->comment_author_IP,
     155                                        'comment_author_url' => (string) $comment->comment_author_url,
     156                                        'comment_date' => (string) $comment->comment_date,
     157                                        'comment_date_gmt' => (string) $comment->comment_date_gmt,
     158                                        'comment_content' => (string) $comment->comment_content,
     159                                        'comment_approved' => (string) $comment->comment_approved,
     160                                        'comment_type' => (string) $comment->comment_type,
     161                                        'comment_parent' => (string) $comment->comment_parent,
     162                                        'comment_user_id' => (int) $comment->comment_user_id,
     163                                );
     164                        }       
     165       
     166                        $posts[] = $post;
     167                }
     168               
     169                return array(
     170                        'authors' => $authors,
     171                        'posts' => $posts,
     172                        'categories' => $categories,
     173                        'tags' => $tags,
     174                        'terms' => $terms,
     175                        'base_url' => $base_url
     176                );
     177        }
     178}
     179
     180/**
     181 * WXR Parser that makes use of the XML Parser PHP extension
     182 *
     183 * @todo wxr checking
     184 */
     185class WXR_Parser_XML {
     186        var $wp_tags = array(
     187                'wp:post_id', 'wp:post_date', 'wp:post_date_gmt', 'wp:comment_status', 'wp:ping_status',
     188                'wp:status', 'wp:post_name', 'wp:post_parent', 'wp:menu_order', 'wp:post_type', 'wp:post_password',
     189                'wp:is_sticky', 'wp:term_id', 'wp:category_nicename', 'wp:category_parent', 'wp:cat_name', 'wp:category_description',
     190                'wp:tag_slug', 'wp:tag_name', 'wp:tag_description', 'wp:term_taxonomy', 'wp:term_parent',
     191                'wp:term_name', 'wp:term_description', 'wp:author_login', 'wp:author_email', 'wp:author_display_name',
     192                'wp:author_first_name', 'wp:author_last_name',
     193        );
     194        var $wp_sub_tags = array(
     195                'wp:comment_id', 'wp:comment_author', 'wp:comment_author_email', 'wp:comment_author_url',
     196                'wp:comment_author_IP', 'wp:comment_date', 'wp:comment_date_gmt', 'wp:comment_content',
     197                'wp:comment_approved', 'wp:comment_type', 'wp:comment_parent', 'wp:comment_user_id',
     198        );
     199
     200        function parse( $file ) {
     201                $this->is_wxr_file = $this->in_post = $this->cdata = $this->data = $this->sub_data = $this->in_tag = $this->in_sub_tag = false;
     202                $this->authors = $this->posts = $this->term = $this->category = $this->tag = array();
     203
     204                $xml = xml_parser_create( 'UTF-8' );
     205                xml_parser_set_option( $xml, XML_OPTION_SKIP_WHITE, 1 );
     206                xml_parser_set_option( $xml, XML_OPTION_CASE_FOLDING, 0 );
     207                xml_set_object( $xml, $this );
     208                xml_set_character_data_handler( $xml, 'cdata' );
     209                xml_set_element_handler( $xml, 'tag_open', 'tag_close' );
     210
     211                if ( ! xml_parse( $xml, file_get_contents( $file ), true ) ) {
     212                        $error_code = xml_get_error_code( $xml );
     213                        $error_string = xml_error_string( $error_code );
     214                        return new WP_Error( 'WXR_parse_error', 'There was an error when reading this WXR file', array( $error_code, $error_string ) );
     215                }
     216                xml_parser_free( $xml );
     217               
     218                if ( ! $this->is_wxr_file )
     219                        return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) );
     220
     221                return array(
     222                        'authors' => $this->authors,
     223                        'posts' => $this->posts,
     224                        'categories' => $this->category,
     225                        'tags' => $this->tag,
     226                        'terms' => $this->term,
     227                        'base_url' => $this->base_url
     228                );
     229        }
     230
     231        function tag_open( $parse, $tag, $attr ) {
     232                if ( in_array( $tag, $this->wp_tags ) ) {
     233                        $this->in_tag = substr( $tag, 3 );
     234                        return;
     235                }
     236
     237                if ( in_array( $tag, $this->wp_sub_tags ) ) {
     238                        $this->in_sub_tag = substr( $tag, 3 );
     239                        return;
     240                }
     241
     242                switch ( $tag ) {
     243                        case 'category':
     244                                if ( isset($attr['domain'], $attr['nicename']) ) {
     245                                        $this->sub_data['domain'] = $attr['domain'];
     246                                        $this->sub_data['slug'] = $attr['nicename'];
     247                                }
     248                                break;
     249                        case 'item': $this->in_post = true;
     250                        case 'title': if ( $this->in_post ) $this->in_tag = 'post_title'; break;
     251                        case 'guid': $this->in_tag = 'guid'; break;
     252                        case 'dc:creator': $this->in_tag = 'post_author'; break;
     253                        case 'content:encoded': $this->in_tag = 'post_content'; break;
     254                        case 'excerpt:encoded': $this->in_tag = 'post_excerpt'; break;
     255
     256                        case 'wp:term_slug': $this->in_tag = 'slug'; break;
     257                        case 'wp:meta_key': $this->in_sub_tag = 'key'; break;
     258                        case 'wp:meta_value': $this->in_sub_tag = 'value'; break;
     259                }
     260        }
     261
     262        function cdata( $parser, $cdata ) {
     263                if ( ! trim( $cdata ) )
     264                        return;
     265
     266                $this->cdata .= trim( $cdata );
     267        }
     268
     269        function tag_close( $parser, $tag ) {
     270                switch ( $tag ) {
     271                        case 'wp:comment':
     272                                if ( ! empty( $this->sub_data ) )
     273                                        $this->data['comments'][] = $this->sub_data;
     274                                $this->sub_data = false;
     275                                break;
     276                        case 'category':
     277                                if ( ! empty( $this->sub_data ) ) {
     278                                        $this->sub_data['name'] = $this->cdata;
     279                                        $this->data['terms'][] = $this->sub_data;
     280                                }
     281                                $this->sub_data = false;
     282                                break;
     283                        case 'wp:postmeta':
     284                                if ( ! empty( $this->sub_data ) )
     285                                        $this->data['postmeta'][] = $this->sub_data;
     286                                $this->sub_data = false;
     287                                break;
     288                        case 'item':
     289                                $this->posts[] = $this->data;
     290                                $this->data = false;
     291                                break;
     292                        case 'wp:category':
     293                        case 'wp:tag':
     294                        case 'wp:term':
     295                                $n = substr( $tag, 3 );
     296                                array_push( $this->$n, $this->data );
     297                                $this->data = false;
     298                                break;
     299                        case 'wp:author':
     300                                if ( ! empty($this->data['author_login']) )
     301                                        $this->authors[$this->data['author_login']] = $this->data;
     302                                $this->data = false;
     303                                break;
     304                        case 'wp:base_site_url':
     305                                $this->base_url = $this->cdata;
     306                                break;
     307                        case 'wp:wxr_version':
     308                                $this->is_wxr_file = preg_match( '/\d+\.\d+/', $this->cdata );
     309                                break;
     310
     311                        default:
     312                                if ( $this->in_sub_tag ) {
     313                                        $this->sub_data[$this->in_sub_tag] = ! empty( $this->cdata ) ? $this->cdata : '';
     314                                        $this->in_sub_tag = false;
     315                                } else if ( $this->in_tag ) {
     316                                        $this->data[$this->in_tag] = ! empty( $this->cdata ) ? $this->cdata : '';
     317                                        $this->in_tag = false;
     318                                }
     319                }
     320
     321                $this->cdata = false;
     322        }
     323}
     324
     325class WXR_Parser_Regex {
     326        function WXR_Parser_Regex() {
     327                $this->__construct();
     328        }
     329       
     330        function __construct() {
     331                $this->has_gzip = is_callable( 'gzopen' );
     332        }
     333
     334        function parse( $file ) {
     335                $is_wxr = $in_post = false;
     336       
     337                $fp = $this->fopen( $file, 'r' );
     338                if ( $fp ) {
     339                        while ( ! $this->feof( $fp ) ) {
     340                                $importline = rtrim( $this->fgets( $fp ) );
     341                               
     342                                if ( ! $is_wxr && preg_match( '|<wp:wxr_version>\d+\.\d+</wp:wxr_version>|', $importline ) )
     343                                        $is_wxr = true;
     344                               
     345                                if ( false !== strpos( $importline, '<wp:base_site_url>' ) ) {
     346                                        preg_match( '|<wp:base_site_url>(.*?)</wp:base_site_url>|is', $importline, $url );
     347                                        $this->base_url = $url[1]; //esc_url (?)
     348                                        continue;
     349                                }
     350                                if ( false !== strpos( $importline, '<wp:category>' ) ) {
     351                                        preg_match( '|<wp:category>(.*?)</wp:category>|is', $importline, $category );
     352                                        $this->categories[] = $this->process_category( $category[1] );
     353                                        continue;
     354                                }
     355                                if ( false !== strpos( $importline, '<wp:tag>' ) ) {
     356                                        preg_match( '|<wp:tag>(.*?)</wp:tag>|is', $importline, $tag );
     357                                        $this->tags[] = $this->process_tag( $tag[1] );
     358                                        continue;
     359                                }
     360                                if ( false !== strpos( $importline, '<wp:term>' ) ) {
     361                                        preg_match( '|<wp:term>(.*?)</wp:term>|is', $importline, $term );
     362                                        $this->terms[] = $this->process_term( $term[1] );
     363                                        continue;
     364                                }
     365                                if ( false !== strpos( $importline, '<wp:author>' ) ) {
     366                                        preg_match( '|<wp:author>(.*?)</wp:author>|is', $importline, $author );
     367                                        $a = $this->process_author( $author[1] );
     368                                        $this->authors[$a['author_login']] = $a;
     369                                        continue;
     370                                }
     371                                if ( false !== strpos( $importline, '<item>' ) ) {
     372                                        $post = '';
     373                                        $in_post = true;
     374                                        continue;
     375                                }
     376                                if ( false !== strpos( $importline, '</item>' ) ) {
     377                                        $in_post = false;
     378                                        $this->posts[] = $this->process_post( $post );
     379                                        continue;
     380                                }
     381                                if ( $in_post ) {
     382                                        $post .= $importline . "\n";
     383                                }
     384                        }
     385
     386                        $this->fclose($fp);
     387                }
     388               
     389                if ( ! $is_wxr )
     390                        return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) );
     391                       
     392                return array(
     393                        'authors' => $this->authors,
     394                        'posts' => $this->posts,
     395                        'categories' => $this->categories,
     396                        'tags' => $this->tags,
     397                        'terms' => $this->terms,
     398                        'base_url' => $this->base_url
     399                );
     400        }
     401       
     402        function get_tag( $string, $tag ) {
     403                global $wpdb;
     404                preg_match( "|<$tag.*?>(.*?)</$tag>|is", $string, $return );
     405                if ( isset( $return[1] ) ) {
     406                        $return = preg_replace( '|^<!\[CDATA\[(.*)\]\]>$|s', '$1', $return[1] );
     407                        $return = $wpdb->escape( trim( $return ) );
     408                } else {
     409                        $return = '';
     410                }
     411                return $return;
     412        }       
     413       
     414        function process_category( $c ) {
     415                return array(
     416                        'term_id' => $this->get_tag( $c, 'wp:term_id' ),
     417                        'cat_name' => $this->get_tag( $c, 'wp:cat_name' ),
     418                        'category_nicename'     => $this->get_tag( $c, 'wp:category_nicename' ),
     419                        'category_parent' => $this->get_tag( $c, 'wp:category_parent' ),
     420                        'category_description' => $this->get_tag( $c, 'wp:category_description' ),
     421                );     
     422        }
     423       
     424        function process_tag( $t ) {
     425                return array(
     426                        'term_id' => $this->get_tag( $t, 'wp:term_id' ),
     427                        'tag_name' => $this->get_tag( $t, 'wp:tag_name' ),
     428                        'tag_slug' => $this->get_tag( $t, 'wp:tag_slug' ),
     429                        'tag_description' => $this->get_tag( $t, 'wp:tag_description' ),
     430                );     
     431        }
     432       
     433        function process_term( $t ) {
     434                return array(
     435                        'term_id' => $this->get_tag( $t, 'wp:term_id' ),
     436                        'term_taxonomy' => $this->get_tag( $t, 'wp:term_taxonomy' ),
     437                        'slug' => $this->get_tag( $t, 'wp:term_slug' ),
     438                        'term_parent' => $this->get_tag( $t, 'wp:term_parent' ),
     439                        'term_name' => $this->get_tag( $t, 'wp:term_name' ),
     440                        'term_description' => $this->get_tag( $t, 'wp:term_description' ),
     441                );     
     442        }
     443       
     444        function process_author( $a ) {
     445                return array(
     446                        'author_login' => $this->get_tag( $a, 'wp:author_login' ),
     447                        'author_email' => $this->get_tag( $a, 'wp:author_email' ),
     448                        'author_display_name' => $this->get_tag( $a, 'wp:author_display_name' ),
     449                        'author_first_name' => $this->get_tag( $a, 'wp:author_first_name' ),
     450                        'author_last_name' => $this->get_tag( $a, 'wp:author_last_name' ),
     451                );
     452        }
     453       
     454        function process_post( $post ) {
     455                $post_id        = $this->get_tag( $post, 'wp:post_id' );
     456                $post_title     = $this->get_tag( $post, 'title' );
     457                $post_date      = $this->get_tag( $post, 'wp:post_date' );
     458                $post_date_gmt  = $this->get_tag( $post, 'wp:post_date_gmt' );
     459                $comment_status = $this->get_tag( $post, 'wp:comment_status' );
     460                $ping_status    = $this->get_tag( $post, 'wp:ping_status' );
     461                $status         = $this->get_tag( $post, 'wp:status' );
     462                $post_name      = $this->get_tag( $post, 'wp:post_name' );
     463                $post_parent    = $this->get_tag( $post, 'wp:post_parent' );
     464                $menu_order     = $this->get_tag( $post, 'wp:menu_order' );
     465                $post_type      = $this->get_tag( $post, 'wp:post_type' );
     466                $post_password  = $this->get_tag( $post, 'wp:post_password' );
     467                $is_sticky              = $this->get_tag( $post, 'wp:is_sticky' );
     468                $guid           = $this->get_tag( $post, 'guid' );
     469                $post_author    = $this->get_tag( $post, 'dc:creator' );
     470
     471                $post_excerpt = $this->get_tag( $post, 'excerpt:encoded' );
     472                $post_excerpt = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_excerpt );
     473                $post_excerpt = str_replace( '<br>', '<br />', $post_excerpt );
     474                $post_excerpt = str_replace( '<hr>', '<hr />', $post_excerpt );
     475
     476                $post_content = $this->get_tag( $post, 'content:encoded' );
     477                $post_content = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_content );
     478                $post_content = str_replace( '<br>', '<br />', $post_content );
     479                $post_content = str_replace( '<hr>', '<hr />', $post_content );
     480               
     481                $postdata = compact( 'post_id', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_excerpt',
     482                        'post_title', 'status', 'post_name', 'comment_status', 'ping_status', 'guid', 'post_parent',
     483                        'menu_order', 'post_type', 'post_password', 'is_sticky'
     484                );
     485               
     486                preg_match_all( '|<category domain="([^"]+?)" nicename="([^"]+?)">(.+?)</category>|is', $post, $terms, PREG_SET_ORDER );
     487                foreach ( $terms as $t ) {
     488                        $post_terms[] = array(
     489                                'slug' => $t[2],
     490                                'domain' => $t[1],
     491                                'name' => str_replace( array( '<![CDATA[', ']]>' ), '', $t[3] ),
     492                        );
     493                }               
     494                if ( ! empty( $post_terms ) ) $postdata['terms'] = $post_terms;
     495               
     496                preg_match_all( '|<wp:comment>(.+?)</wp:comment>|is', $post, $comments );
     497                $comments = $comments[1];
     498                if ( $comments ) {
     499                        foreach ( $comments as $comment ) {
     500                                $post_comments[] = array(
     501                                        'comment_id' => $this->get_tag( $comment, 'wp:comment_id' ),
     502                                        'comment_author' => $this->get_tag( $comment, 'wp:comment_author' ),
     503                                        'comment_author_email' => $this->get_tag( $comment, 'wp:comment_author_email' ),
     504                                        'comment_author_IP' => $this->get_tag( $comment, 'wp:comment_author_IP' ),
     505                                        'comment_author_url' => $this->get_tag( $comment, 'wp:comment_author_url' ),
     506                                        'comment_date' => $this->get_tag( $comment, 'wp:comment_date' ),
     507                                        'comment_date_gmt' => $this->get_tag( $comment, 'wp:comment_date_gmt' ),
     508                                        'comment_content' => $this->get_tag( $comment, 'wp:comment_content' ),
     509                                        'comment_approved' => $this->get_tag( $comment, 'wp:comment_approved' ),
     510                                        'comment_type' => $this->get_tag( $comment, 'wp:comment_type' ),
     511                                        'comment_parent' => $this->get_tag( $comment, 'wp:comment_parent' ),                           
     512                                );
     513                        }
     514                }               
     515                if ( ! empty( $post_comments ) ) $postdata['comments'] = $post_comments;
     516               
     517                preg_match_all( '|<wp:postmeta>(.+?)</wp:postmeta>|is', $post, $postmeta );
     518                $postmeta = $postmeta[1];
     519                if ( $postmeta) {
     520                        foreach ( $postmeta as $p ) {
     521                                $post_postmeta[] = array(
     522                                        'key' => $this->get_tag( $p, 'wp:meta_key' ),
     523                                        'value' => $this->get_tag( $p, 'wp:meta_value' ),
     524                                );
     525                        }
     526                }               
     527                if ( ! empty( $post_postmeta ) ) $postdata['postmeta'] = $post_postmeta;
     528               
     529                return $postdata;
     530        }
     531       
     532        function _normalize_tag( $matches ) {
     533                return '<' . strtolower( $matches[1] );
     534        }       
     535
     536        function fopen( $filename, $mode = 'r' ) {
     537                if ( $this->has_gzip )
     538                        return gzopen( $filename, $mode );
     539                return fopen( $filename, $mode );
     540        }
     541
     542        function feof( $fp ) {
     543                if ( $this->has_gzip )
     544                        return gzeof( $fp );
     545                return feof( $fp );
     546        }
     547
     548        function fgets( $fp, $len = 8192 ) {
     549                if ( $this->has_gzip )
     550                        return gzgets( $fp, $len );
     551                return fgets( $fp, $len );
     552        }
     553
     554        function fclose( $fp ) {
     555                if ( $this->has_gzip )
     556                        return gzclose( $fp );
     557                return fclose( $fp );
     558        }
     559}
  • trunk/readme.txt

     
    33Donate link:
    44Tags: importer, wordpress
    55Requires at least: 3.0
    6 Tested up to: 3.0
     6Tested up to: 3.0.1
    77Stable tag: 0.2
    88
    9 Import posts, pages, comments, custom fields, categories, and tags from a WordPress export file.
     9Import posts, pages, comments, custom fields, categories, tags and more from a WordPress export file.
    1010
    1111== Description ==
    1212
    13 Import posts, pages, comments, custom fields, categories, and tags from a WordPress export file.
     13Import posts, pages, comments, custom fields, categories, tags and more from a WordPress export file.
    1414
    1515== Installation ==
    1616
    17171. Upload the `wordpress-importer` folder to the `/wp-content/plugins/` directory
    18181. Activate the plugin through the 'Plugins' menu in WordPress
    19 1. Go to the Tools -> Import screen, Click on WordPress
     191. Go to the Tools -> Import screen, click on WordPress
    2020
    21 == Frequently Asked Questions ==
     21== Changelog ==
    2222
    23 == Screenshots ==
     23= 0.3 =
     24* Use an XML Parser if possible
     25* Proper import support for nav menus
     26* ... and more
    2427
    25 == Changelog ==
    26 
    2728= 0.1 =
    2829* Initial release
     30
     31== Upgrade Notice ==
     32
     33= 0.3 =
     34Upgrade for a more robust and reliable experience when importing WordPress export file.
     35
     36== Filters ==
     37
     38The importer has a couple of filters to allow you to completely enable/block certain features:
     39* `import_allow_create_users`: return false if you only want to allow mapping to existing users
     40* `import_allow_fetch_attachments`: return false if you do not wish to allow importing and downloading of attachments
  • trunk/wordpress-importer.php

     
    22/*
    33Plugin Name: WordPress Importer
    44Plugin URI: http://wordpress.org/extend/plugins/wordpress-importer/
    5 Description: Import posts, pages, comments, custom fields, categories, and tags from a WordPress export file.
     5Description: Import posts, pages, comments, custom fields, categories, tags and more from a WordPress export file.
    66Author: wordpressdotorg
    77Author URI: http://wordpress.org/
    8 Version: 0.2
    9 Stable tag: 0.2
     8Version: 0.3
    109License: GPL v2 - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
    1110*/
    1211
    13 if ( !defined('WP_LOAD_IMPORTERS') )
     12if ( ! defined( 'WP_LOAD_IMPORTERS' ) )
    1413        return;
    1514
    1615// Load Importer API
    1716require_once ABSPATH . 'wp-admin/includes/import.php';
    1817
    19 if ( !class_exists( 'WP_Importer' ) ) {
     18if ( ! class_exists( 'WP_Importer' ) ) {
    2019        $class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php';
    2120        if ( file_exists( $class_wp_importer ) )
    22                 require_once $class_wp_importer;
     21                require $class_wp_importer;
    2322}
    2423
     24// include WXR file parsers
     25require dirname( __FILE__ ) . '/parsers.php';
     26
    2527/**
    26  * WordPress Importer
     28 * WordPress Importer class for managing the import process of a WXR file
    2729 *
    2830 * @package WordPress
    2931 * @subpackage Importer
    3032 */
    3133if ( class_exists( 'WP_Importer' ) ) {
    3234class WP_Import extends WP_Importer {
    33 
    34         var $post_ids_processed = array ();
    35         var $orphans = array ();
    36         var $file;
    3735        var $id;
    38         var $mtnames = array ();
    39         var $newauthornames = array ();
    40         var $allauthornames = array ();
    4136
    42         var $author_ids = array ();
    43         var $tags = array ();
    44         var $categories = array ();
    45         var $terms = array ();
    46         var $authors = array ();
     37        var $authors = array();
     38        var $posts = array();
     39        var $terms = array();
     40        var $categories = array();
     41        var $tags = array();
     42        var $base_url = '';
    4743
    48         var $j = -1;
     44        var $processed_authors = array();
     45        var $processed_terms = array();
     46        var $processed_posts = array();
     47        var $post_orphans = array();
     48        var $processed_menu_items = array();
     49        var $menu_item_orphans = array();
     50        var $missing_menu_items = array();
     51
    4952        var $fetch_attachments = false;
    50         var $url_remap = array ();
     53        var $url_remap = array();
    5154
    52         function header() {
    53                 echo '<div class="wrap">';
    54                 screen_icon();
    55                 echo '<h2>'.__('Import WordPress', 'wordpress-importer').'</h2>';
    56         }
     55        function WP_Import() { /* nothing */ }
    5756
    58         function footer() {
    59                 echo '</div>';
     57        function dispatch() {
     58                $this->header();
     59               
     60                $step = empty( $_GET['step'] ) ? 0 : (int) $_GET['step'];               
     61                switch ( $step ) {
     62                        case 0:
     63                                $this->greet();
     64                                break;
     65                        case 1:
     66                                check_admin_referer( 'import-upload' );
     67                                if ( $this->handle_upload() )
     68                                        $this->import_options();
     69                                break;
     70                        case 2:
     71                                check_admin_referer( 'import-wordpress' );
     72                                $this->fetch_attachments = ( ! empty( $_POST['fetch_attachments'] ) && $this->allow_fetch_attachments() );
     73                                $this->id = (int) $_POST['import_id'];
     74                                $file = get_attached_file( $this->id );
     75                                $this->import( $file );
     76                                break;
     77                }
     78               
     79                $this->footer();
    6080        }
    6181
    62         function greet() {
    63                 echo '<div class="narrow">';
    64                 echo '<p>'.__('Howdy! Upload your WordPress eXtended RSS (WXR) file and we&#8217;ll import the posts, pages, comments, custom fields, categories, and tags into this site.', 'wordpress-importer').'</p>';
    65                 echo '<p>'.__('Choose a WordPress WXR file to upload, then click Upload file and import.', 'wordpress-importer').'</p>';
    66                 wp_import_upload_form("admin.php?import=wordpress&amp;step=1");
    67                 echo '</div>';
    68         }
     82        function import( $file ) {
     83                add_filter( 'import_post_meta_key', array( $this, 'is_valid_meta_key' ) );
    6984
    70         function get_tag( $string, $tag ) {
    71                 global $wpdb;
    72                 preg_match("|<$tag.*?>(.*?)</$tag>|is", $string, $return);
    73                 if ( isset($return[1]) ) {
    74                         $return = preg_replace('|^<!\[CDATA\[(.*)\]\]>$|s', '$1', $return[1]);
    75                         $return = $wpdb->escape( trim( $return ) );
    76                 } else {
    77                         $return = '';
    78                 }
    79                 return $return;
    80         }
     85                $this->import_start( $file );
    8186
    82         function has_gzip() {
    83                 return is_callable('gzopen');
    84         }
     87                $this->get_author_mapping();
    8588
    86         function fopen($filename, $mode='r') {
    87                 if ( $this->has_gzip() )
    88                         return gzopen($filename, $mode);
    89                 return fopen($filename, $mode);
    90         }
     89                wp_suspend_cache_invalidation( true );
     90                $this->process_categories();
     91                $this->process_tags();
     92                $this->process_terms();
     93                $this->process_posts();
     94                wp_suspend_cache_invalidation( false );
    9195
    92         function feof($fp) {
    93                 if ( $this->has_gzip() )
    94                         return gzeof($fp);
    95                 return feof($fp);
    96         }
     96                // update items with missing/incorrect parent IDs
     97                $this->backfill_parents();
     98                // update attachment references within posts and postmeta
     99                $this->backfill_attachment_urls();
    97100
    98         function fgets($fp, $len=8192) {
    99                 if ( $this->has_gzip() )
    100                         return gzgets($fp, $len);
    101                 return fgets($fp, $len);
     101                $this->import_end();
    102102        }
    103103
    104         function fclose($fp) {
    105                 if ( $this->has_gzip() )
    106                         return gzclose($fp);
    107                 return fclose($fp);
    108         }
    109 
    110         function get_entries($process_post_func=NULL) {
    111                 set_magic_quotes_runtime(0);
    112 
    113                 $doing_entry = false;
    114                 $is_wxr_file = false;
    115 
    116                 $fp = $this->fopen($this->file, 'r');
    117                 if ($fp) {
    118                         while ( !$this->feof($fp) ) {
    119                                 $importline = rtrim($this->fgets($fp));
    120 
    121                                 // this doesn't check that the file is perfectly valid but will at least confirm that it's not the wrong format altogether
    122                                 if ( !$is_wxr_file && preg_match('|xmlns:wp="http://wordpress[.]org/export/\d+[.]\d+/"|', $importline) )
    123                                         $is_wxr_file = true;
    124 
    125                                 if ( false !== strpos($importline, '<wp:base_site_url>') ) {
    126                                         preg_match('|<wp:base_site_url>(.*?)</wp:base_site_url>|is', $importline, $url);
    127                                         $this->base_url = $url[1];
    128                                         continue;
    129                                 }
    130                                 if ( false !== strpos($importline, '<wp:category>') ) {
    131                                         preg_match('|<wp:category>(.*?)</wp:category>|is', $importline, $category);
    132                                         $this->categories[] = $category[1];
    133                                         continue;
    134                                 }
    135                                 if ( false !== strpos($importline, '<wp:tag>') ) {
    136                                         preg_match('|<wp:tag>(.*?)</wp:tag>|is', $importline, $tag);
    137                                         $this->tags[] = $tag[1];
    138                                         continue;
    139                                 }
    140                                 if ( false !== strpos($importline, '<wp:term>') ) {
    141                                         preg_match('|<wp:term>(.*?)</wp:term>|is', $importline, $term);
    142                                         $this->terms[] = $term[1];
    143                                         continue;
    144                                 }
    145                                 if ( false !== strpos($importline, '<wp:author>') ) {
    146                                         preg_match('|<wp:author>(.*?)</wp:author>|is', $importline, $author);
    147                                         $this->authors[] = $author[1];
    148                                         continue;
    149                                 }
    150                                 if ( false !== strpos($importline, '<item>') ) {
    151                                         $this->post = '';
    152                                         $doing_entry = true;
    153                                         continue;
    154                                 }
    155                                 if ( false !== strpos($importline, '</item>') ) {
    156                                         $doing_entry = false;
    157                                         if ($process_post_func)
    158                                                 call_user_func($process_post_func, $this->post);
    159                                         continue;
    160                                 }
    161                                 if ( $doing_entry ) {
    162                                         $this->post .= $importline . "\n";
    163                                 }
    164                         }
    165 
    166                         $this->fclose($fp);
     104        function import_start( $file ) {
     105                $import_arr = $this->parse( $file );
     106                               
     107                if ( is_wp_error( $import_arr ) ) {
     108                        echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong></p>';
     109                        echo '<p>' . esc_html( $import_arr->get_error_message() ) . '</p>';
     110                        $this->footer();
     111                        die();                 
    167112                }
     113               
     114                $this->authors = $import_arr['authors'];
     115                $this->posts = $import_arr['posts'];
     116                $this->terms = $import_arr['terms'];
     117                $this->categories = $import_arr['categories'];
     118                $this->tags = $import_arr['tags'];
     119                $this->base_url = esc_url( $import_arr['base_url'] );
    168120
    169                 return $is_wxr_file;
     121                wp_defer_term_counting( true );
     122                wp_defer_comment_counting( true );
    170123
     124                do_action( 'import_start' );
    171125        }
    172126
    173         function get_wp_authors() {
    174                 // We need to find unique values of author names, while preserving the order, so this function emulates the unique_value(); php function, without the sorting.
    175                 $temp = $this->allauthornames;
    176                 $authors[0] = array_shift($temp);
    177                 $y = count($temp) + 1;
    178                 for ($x = 1; $x < $y; $x ++) {
    179                         $next = array_shift($temp);
    180                         if (!(in_array($next, $authors)))
    181                                 array_push($authors, $next);
     127        function import_end() {
     128                wp_import_cleanup( $this->id );
     129
     130                wp_cache_flush();
     131                foreach ( get_taxonomies() as $tax ) {
     132                        delete_option( "{$tax}_children" );
     133                        _get_term_hierarchy( $tax );
    182134                }
     135               
     136                wp_defer_term_counting( false );
     137                wp_defer_comment_counting( false );
     138               
     139                echo '<p>' . __( 'All done.' ) . ' <a href="' . admin_url() . '">' . __( 'Have fun!' ) . '</a>' . '</p>';
    183140
    184                 return $authors;
     141                do_action( 'import_end' );
    185142        }
    186143
    187         function get_authors_from_post() {
    188                 global $current_user;
     144        function handle_upload() {
     145                $file = wp_import_handle_upload();
    189146
    190                 // this will populate $this->author_ids with a list of author_names => user_ids
     147                if ( isset( $file['error'] ) ) {
     148                        echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong></p>';
     149                        echo '<p>' . esc_html( $file['error'] ) . '</p>';
     150                        return false;
     151                }
    191152
    192                 foreach ( (array) $_POST['author_in'] as $i => $in_author_name ) {
    193 
    194                         if ( !empty($_POST['user_select'][$i]) ) {
    195                                 // an existing user was selected in the dropdown list
    196                                 $user = get_userdata( intval($_POST['user_select'][$i]) );
    197                                 if ( isset($user->ID) )
    198                                         $this->author_ids[$in_author_name] = $user->ID;
    199                         }
    200                         elseif ( $this->allow_create_users() ) {
    201                                 // nothing was selected in the dropdown list, so we'll use the name in the text field
    202 
    203                                 $new_author_name = trim($_POST['user_create'][$i]);
    204                                 // if the user didn't enter a name, assume they want to use the same name as in the import file
    205                                 if ( empty($new_author_name) )
    206                                         $new_author_name = $in_author_name;
    207 
    208                                 $user_id = username_exists($new_author_name);
    209                                 if ( !$user_id ) {
    210                                         $user_id = wp_create_user($new_author_name, wp_generate_password());
    211                                 }
    212 
    213                                 if ( !is_wp_error( $user_id ) ) {
    214                                         $this->author_ids[$in_author_name] = $user_id;
    215                                 }
    216                         }
    217 
    218                         // failsafe: if the user_id was invalid, default to the current user
    219                         if ( empty($this->author_ids[$in_author_name]) ) {
    220                                 $this->author_ids[$in_author_name] = intval($current_user->ID);
    221                         }
     153                $this->id = (int) $file['id'];
     154                $import_data = $this->parse( $file['file'] );
     155                if ( is_wp_error( $import_data ) ) {
     156                        echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong></p>';
     157                        echo '<p>' . esc_html( $import_data->get_error_message() ) . '</p>';
     158                        return false;
    222159                }
     160                $this->authors = $import_data['authors'];
    223161
     162                return true;
    224163        }
    225164
    226         function wp_authors_form() {
     165        function import_options() {
     166                $j = 0;
    227167?>
    228 <h2><?php _e('Assign Authors', 'wordpress-importer'); ?></h2>
    229 <p><?php _e('To make it easier for you to edit and save the imported posts and drafts, you may want to change the name of the author of the posts. For example, you may want to import all the entries as <code>admin</code>s entries.', 'wordpress-importer'); ?></p>
    230 <?php
    231         if ( $this->allow_create_users() ) {
    232                 echo '<p>'.__('If a new user is created by WordPress, a password will be randomly generated. Manually change the user&#8217;s details if necessary.', 'wordpress-importer')."</p>\n";
    233         }
     168<form action="<?php echo admin_url( 'admin.php?import=wordpress&amp;step=2' ); ?>" method="post">
     169        <?php wp_nonce_field( 'import-wordpress' ); ?>
     170        <input type="hidden" name="import_id" value="<?php echo $this->id; ?>" />
    234171
     172<?php if ( ! empty( $this->authors ) ) : ?>
     173        <h3><?php _e('Assign Authors', 'wordpress-importer'); ?></h3>
     174        <p><?php _e( 'To make it easier for you to edit and save the imported content, you may want to reassign the author of the imported item to an existing user of this site. For example, you may want to import all the entries as <code>admin</code>s entries.', 'wordpress-importer' ); ?></p>
     175<?php if ( $this->allow_create_users() ) : ?>
     176        <p><?php printf( __( 'If a new user is created by WordPress, a new password will be randomly generated and the new user&#8217;s role will be set as %s. Manually changing the new user&#8217;s details will be necessary.', 'wordpress-importer' ), esc_html( get_option('default_role') ) ); ?></p>
     177<?php endif; ?>
     178        <ol id="authors">
     179<?php foreach ( $this->authors as $author ) : ?>
     180                <li><?php $this->author_select( $j++, $author ); ?></li>
     181<?php endforeach; ?>
     182        </ol>
     183<?php endif; ?>
    235184
    236                 $authors = $this->get_wp_authors();
    237                 echo '<form action="?import=wordpress&amp;step=2&amp;id=' . $this->id . '" method="post">';
    238                 wp_nonce_field('import-wordpress');
    239 ?>
    240 <ol id="authors">
    241 <?php
    242                 $j = -1;
    243                 foreach ($authors as $author) {
    244                         ++ $j;
    245                         echo '<li>'.__('Import author:', 'wordpress-importer').' <strong>'.$author.'</strong><br />';
    246                         $this->users_form($j, $author);
    247                         echo '</li>';
    248                 }
     185<?php if ( $this->allow_fetch_attachments() ) : ?>
     186        <h3><?php _e('Import Attachments', 'wordpress-importer'); ?></h3>
     187        <p>
     188                <input type="checkbox" value="1" name="fetch_attachments" id="import-attachments" />
     189                <label for="import-attachments"><?php _e( 'Download and import file attachments', 'wordpress-importer' ); ?></label>
     190        </p>
     191<?php endif; ?>
    249192
    250                 if ( $this->allow_fetch_attachments() ) {
    251 ?>
    252 </ol>
    253 <h2><?php _e('Import Attachments', 'wordpress-importer'); ?></h2>
    254 <p>
    255         <input type="checkbox" value="1" name="attachments" id="import-attachments" />
    256         <label for="import-attachments"><?php _e('Download and import file attachments', 'wordpress-importer') ?></label>
    257 </p>
    258 
     193        <p class="submit"><input type="submit" class="button" value="<?php esc_attr_e( 'Submit', 'wordpress-importer' ); ?>" /></p>
     194</form>
    259195<?php
    260                 }
     196        }
    261197
    262                 echo '<p class="submit">';
    263                 echo '<input type="submit" class="button" value="'. esc_attr__('Submit', 'wordpress-importer') .'" />'.'<br />';
    264                 echo '</p>';
    265                 echo '</form>';
     198        function author_select( $n, $author ) {
     199                if ( $this->allow_create_users() )
     200                        printf( __( 'Import author %1$s or map to existing user', 'wordpress-importer' ), '<strong>' . esc_html( $author['author_display_name'] ) . '</strong>' );
     201                else
     202                        printf( __( 'Map author %1$s to existing user', 'wordpress-importer' ), '<strong>' . esc_html( $author['author_display_name'] ) . '</strong>' );
    266203
     204                $users = get_users_of_blog(); ?>
     205                <input type="hidden" name="imported_authors[<?php echo $n; ?>]" value="<?php echo esc_attr( $author['author_login'] ); ?>" />
     206                <select name="user_map[<?php echo $n; ?>]">
     207                        <option value="0"><?php _e( '- Select -', 'wordpress-importer' ); ?></option>
     208                        <?php foreach ( $users as $user ) : ?>
     209                        <option value="<?php echo intval($user->ID); ?>"><?php echo esc_html( $user->display_name ); ?></option>
     210                        <?php endforeach; ?>
     211                </select>
     212<?php
    267213        }
    268214
    269         function users_form($n, $author) {
     215        function get_author_mapping() {
     216                if ( ! isset( $_POST['imported_authors'] ) )
     217                        return;
    270218
    271                 if ( $this->allow_create_users() ) {
    272                         printf('<label>'.__('Create user %1$s or map to existing', 'wordpress-importer'), ' <input type="text" value="'. esc_attr($author) .'" name="'.'user_create['.intval($n).']'.'" maxlength="30" /></label> <br />');
    273                 }
    274                 else {
    275                         echo __('Map to existing', 'wordpress-importer').'<br />';
    276                 }
     219                foreach ( (array) $_POST['imported_authors'] as $i => $login ) {
     220                        $login = sanitize_user( $login, true );
    277221
    278                 // keep track of $n => $author name
    279                 echo '<input type="hidden" name="author_in['.intval($n).']" value="' . esc_attr($author).'" />';
     222                        if ( ! empty( $_POST['user_map'][$i] ) ) {
     223                                $user = get_userdata( intval($_POST['user_map'][$i]) );
     224                                if ( isset( $user->ID ) )
     225                                        $this->processed_authors[$login] = $user->ID;
     226                        } else if ( $this->allow_create_users() ) {
     227                                $user_id = username_exists( $login );
     228                                if ( ! $user_id ) {
     229                                        $user_data = array(
     230                                                'user_login' => $login,
     231                                                'user_pass' => wp_generate_password(),
     232                                                'user_email' => $this->authors[$login]['author_email'],
     233                                                'display_name' => $this->authors[$login]['author_display_name'],
     234                                                'first_name' => $this->authors[$login]['author_first_name'],
     235                                                'last_name' => $this->authors[$login]['author_last_name'],
     236                                        );
     237                                        $user_id = wp_insert_user( $user_data );
     238                                }
    280239
    281                 $users = get_users_of_blog();
    282 ?><select name="user_select[<?php echo $n; ?>]">
    283         <option value="0"><?php _e('- Select -', 'wordpress-importer'); ?></option>
    284         <?php
    285                 foreach ($users as $user) {
    286                         echo '<option value="'.$user->user_id.'">'.$user->user_login.'</option>';
    287                 }
    288 ?>
    289         </select>
    290         <?php
    291         }
     240                                if ( ! is_wp_error( $user_id ) )
     241                                        $this->processed_authors[$login] = $user_id;
     242                        }
    292243
    293         function select_authors() {
    294                 $is_wxr_file = $this->get_entries(array(&$this, 'process_author'));
    295                 if ( $is_wxr_file ) {
    296                         $this->wp_authors_form();
     244                        // failsafe: if the user_id was invalid, default to the current user
     245                        if ( empty( $this->processed_authors[$login] ) )
     246                                $this->processed_authors[$login] = (int) get_current_user_id();
    297247                }
    298                 else {
    299                         echo '<h2>'.__('Invalid file', 'wordpress-importer').'</h2>';
    300                         echo '<p>'.__('Please upload a valid WXR (WordPress eXtended RSS) export file.', 'wordpress-importer').'</p>';
    301                 }
    302248        }
    303249
    304         // fetch the user ID for a given author name, respecting the mapping preferences
    305         function checkauthor($author) {
    306                 global $current_user;
    307 
    308                 if ( !empty($this->author_ids[$author]) )
    309                         return $this->author_ids[$author];
    310 
    311                 // failsafe: map to the current user
    312                 return $current_user->ID;
    313         }
    314 
    315 
    316 
    317250        function process_categories() {
    318                 global $wpdb;
     251                if ( empty( $this->categories ) )
     252                        return;
    319253
    320                 $cat_names = (array) get_terms('category', array('fields' => 'names'));
    321 
    322                 while ( $c = array_shift($this->categories) ) {
    323                         $cat_name = trim($this->get_tag( $c, 'wp:cat_name' ));
    324 
    325                         // If the category exists we leave it alone
    326                         if ( in_array($cat_name, $cat_names) )
     254                foreach ( $this->categories as $cat ) {
     255                        // if the category already exists leave it alone
     256                        $term_id = term_exists( $cat['category_nicename'], 'category' );
     257                        if ( $term_id ) {
     258                                if ( is_array($term_id) ) $term_id = $term_id['term_id'];
     259                                $this->processed_terms[intval($cat['term_id'])] = (int) $term_id;
    327260                                continue;
     261                        }
    328262
    329                         $category_nicename      = $this->get_tag( $c, 'wp:category_nicename' );
    330                         $category_description = $this->get_tag( $c, 'wp:category_description' );
    331                         $posts_private          = (int) $this->get_tag( $c, 'wp:posts_private' );
    332                         $links_private          = (int) $this->get_tag( $c, 'wp:links_private' );
     263                        $category_parent = empty( $cat['category_parent'] ) ? 0 : category_exists( $cat['category_parent'] );
     264                        $category_description = isset( $cat['category_description'] ) ? $cat['category_description'] : '';
     265                        $catarr = array(
     266                                'category_nicename' => $cat['category_nicename'],
     267                                'category_parent' => $category_parent,
     268                                'cat_name' => $cat['cat_name'],
     269                                'category_description' => $category_description
     270                        );
    333271
    334                         $parent = $this->get_tag( $c, 'wp:category_parent' );
    335 
    336                         if ( empty($parent) )
    337                                 $category_parent = '0';
    338                         else
    339                                 $category_parent = category_exists($parent);
    340 
    341                         $catarr = compact('category_nicename', 'category_parent', 'posts_private', 'links_private', 'posts_private', 'cat_name', 'category_description');
    342 
    343                         print '<em>' . sprintf( __( 'Importing category <em>%s</em>&#8230;' , 'wordpress-importer'), esc_html($cat_name) ) . '</em><br />' . "\n";
    344                         $cat_ID = wp_insert_category($catarr);
     272                        $id = wp_insert_category( $catarr );
     273                        if ( ! is_wp_error( $id ) ) {
     274                                $this->processed_terms[intval($cat['term_id'])] = $id;
     275                        } else {
     276                                echo 'Error importing category: ' . $id->get_error_message() . '<br />';
     277                                continue;
     278                        }                               
    345279                }
    346280        }
    347281
    348282        function process_tags() {
    349                 global $wpdb;
     283                if ( empty( $this->tags ) )
     284                        return;
    350285
    351                 $tag_names = (array) get_terms('post_tag', array('fields' => 'names'));
    352 
    353                 while ( $c = array_shift($this->tags) ) {
    354                         $tag_name = trim($this->get_tag( $c, 'wp:tag_name' ));
    355 
    356                         // If the category exists we leave it alone
    357                         if ( in_array($tag_name, $tag_names) )
     286                foreach ( $this->tags as $tag ) {
     287                        // if the tag already exists leave it alone
     288                        $term_id = term_exists( $tag['tag_slug'], 'post_tag' );         
     289                        if ( $term_id ) {
     290                                if ( is_array($term_id) ) $term_id = $term_id['term_id'];       
     291                                $this->processed_terms[intval($tag['term_id'])] = (int) $term_id;
    358292                                continue;
     293                        }
    359294
    360                         $slug = $this->get_tag( $c, 'wp:tag_slug' );
    361                         $description = $this->get_tag( $c, 'wp:tag_description' );
     295                        $tag_desc = isset( $tag['tag_description'] ) ? $tag['tag_description'] : '';
     296                        $tagarr = array( 'slug' => $tag['tag_slug'], 'description' => $tag_desc );
    362297
    363                         $tagarr = compact('slug', 'description');
    364 
    365                         print '<em>' . sprintf( __( 'Importing tag <em>%s</em>&#8230;' , 'wordpress-importer'), esc_html($tag_name) ) . '</em><br />' . "\n";
    366                         $tag_ID = wp_insert_term($tag_name, 'post_tag', $tagarr);
     298                        $id = wp_insert_term( $tag['tag_name'], 'post_tag', $tagarr );
     299                        if ( ! is_wp_error( $id ) ) {
     300                                $this->processed_terms[intval($tag['term_id'])] = $id['term_id'];
     301                        } else {
     302                                echo 'Error importing post tag: ' . $id->get_error_message() . '<br />';
     303                                continue;
     304                        }                       
    367305                }
    368306        }
    369307
    370308        function process_terms() {
    371                 global $wpdb, $wp_taxonomies;
     309                if ( empty( $this->terms ) )
     310                        return;
    372311
    373                 $custom_taxonomies = $wp_taxonomies;
    374                 // get rid of the standard taxonomies
    375                 unset( $custom_taxonomies['category'] );
    376                 unset( $custom_taxonomies['post_tag'] );
    377                 unset( $custom_taxonomies['link_category'] );
     312                foreach ( $this->terms as $term ) {
     313                        // if the term already exists in the correct taxonomy leave it alone
     314                        $term_id = term_exists( $term['slug'], $term['term_taxonomy'] );
     315                        if ( $term_id ) {
     316                                if ( is_array($term_id) ) $term_id = $term_id['term_id'];
     317                                $this->processed_terms[intval($term['term_id'])] = (int) $term_id;
     318                                continue;
     319                        }
    378320
    379                 $custom_taxonomies = array_keys( $custom_taxonomies );
    380                 $current_terms = (array) get_terms( $custom_taxonomies, array('get' => 'all') );
    381                 $taxonomies = array();
    382                 foreach ( $current_terms as $term ) {
    383                         if ( isset( $_terms[$term->taxonomy] ) ) {
    384                                 $taxonomies[$term->taxonomy] = array_merge( $taxonomies[$term->taxonomy], array($term->name) );
     321                        if ( empty( $term['term_parent'] ) ) {
     322                                $parent = 0;
    385323                        } else {
    386                                 $taxonomies[$term->taxonomy] = array($term->name);
     324                                $parent = term_exists( $term['term_parent'], $term['term_taxonomy'] );
     325                                if ( is_array( $parent ) ) $parent = $parent['term_id'];
    387326                        }
    388                 }
     327                        $description = isset( $term['term_description'] ) ? $term['term_description'] : '';
     328                        $termarr = array( 'slug' => $term['slug'], 'description' => $description, 'parent' => intval($parent) );
    389329
    390                 while ( $c = array_shift($this->terms) ) {
    391                         $term_name = trim($this->get_tag( $c, 'wp:term_name' ));
    392                         $term_taxonomy = trim($this->get_tag( $c, 'wp:term_taxonomy' ));
    393 
    394                         // If the term exists in the taxonomy we leave it alone
    395                         if ( isset($taxonomies[$term_taxonomy] ) && in_array( $term_name, $taxonomies[$term_taxonomy] ) )
     330                        $id = wp_insert_term( $term['term_name'], $term['term_taxonomy'], $termarr );
     331                        if ( ! is_wp_error( $id ) ) {
     332                                $this->processed_terms[intval($term['term_id'])] = $id['term_id'];
     333                        } else {
     334                                echo 'Error importing term: ' . $id->get_error_message() . '<br />';
    396335                                continue;
    397 
    398                         $slug = $this->get_tag( $c, 'wp:term_slug' );
    399                         $description = $this->get_tag( $c, 'wp:term_description' );
    400 
    401                         $termarr = compact('slug', 'description');
    402 
    403                         print '<em>' . sprintf( __( 'Importing <em>%s</em>&#8230;' , 'wordpress-importer'), esc_html($term_name) ) . '</em><br />' . "\n";
    404                         $term_ID = wp_insert_term($term_name, $this->get_tag( $c, 'wp:term_taxonomy' ), $termarr);
     336                        }
    405337                }
    406338        }
    407339
    408         function process_author($post) {
    409                 $author = $this->get_tag( $post, 'dc:creator' );
    410                 if ($author)
    411                         $this->allauthornames[] = $author;
    412         }
    413 
     340        // extract and compact (?)
    414341        function process_posts() {
    415                 echo '<ol>';
     342                foreach ( $this->posts as $post ) {
     343                        if ( isset( $this->processed_posts[$post['post_id']] ) )
     344                                continue;
    416345
    417                 $this->get_entries(array(&$this, 'process_post'));
     346                        if ( 'nav_menu_item' == $post['post_type'] ) {
     347                                $this->process_menu_item( $post );
     348                                continue;
     349                        }
    418350
    419                 echo '</ol>';
     351                        $post_exists = post_exists( $post['post_title'], '', $post['post_date'] );
     352                        if ( $post_exists ) {
     353                                $comment_post_ID = $post_id = $post_exists;
     354                        } else {
     355                                $post_parent = (int) $post['post_parent'];
     356                                if ( $post_parent ) {
     357                                        // if we already know the parent, map it to the new local ID
     358                                        if ( isset( $this->processed_posts[$post_parent] ) ) {
     359                                                $post_parent = $this->processed_posts[$post_parent];
     360                                        // otherwise record the parent for later
     361                                        } else {
     362                                                $this->post_orphans[intval($post['post_id'])] = $post_parent;
     363                                                $post_parent = 0;
     364                                        }
     365                                }
    420366
    421                 wp_import_cleanup($this->id);
    422                 do_action('import_done', 'wordpress');
     367                                // map the post author
     368                                if ( isset( $this->processed_authors[$post['post_author']] ) )
     369                                        $author = $this->processed_authors[$post['post_author']];
     370                                else
     371                                        $author = (int) get_current_user_id();
    423372
    424                 echo '<h3>'.sprintf(__('All done.', 'wordpress-importer').' <a href="%s">'.__('Have fun!', 'wordpress-importer').'</a>', get_option('home')).'</h3>';
    425         }
     373                                $postdata = array(
     374                                        'import_id' => $post['post_id'], 'post_author' => $author, 'post_date' => $post['post_date'],
     375                                        'post_date_gmt' => $post['post_date_gmt'], 'post_content' => $post['post_content'],
     376                                        'post_excerpt' => $post['post_excerpt'], 'post_title' => $post['post_title'],
     377                                        'post_status' => $post['status'], 'post_name' => $post['post_name'],
     378                                        'comment_status' => $post['comment_status'], 'ping_status' => $post['ping_status'],
     379                                        'guid' => $post['guid'], 'post_parent' => $post_parent, 'menu_order' => $post['menu_order'],
     380                                        'post_type' => $post['post_type'], 'post_password' => $post['post_password']
     381                                );
     382                               
     383                                if ( 'attachment' == $postdata['post_type'] ) {
     384                                        $remote_url = ! empty($post['attachment_url']) ? $post['attachment_url'] : $post['guid'];
     385                                        $comment_post_ID = $post_id = $this->process_attachment( $postdata, $remote_url );
     386                                } else {
     387                                        $comment_post_ID = $post_id = wp_insert_post( $postdata, true );
     388                                }
    426389
    427         function _normalize_tag( $matches ) {
    428                 return '<' . strtolower( $matches[1] );
    429         }
    430 
    431         function process_post($post) {
    432                 global $wpdb;
    433 
    434                 $post_ID = (int) $this->get_tag( $post, 'wp:post_id' );
    435                 if ( $post_ID && !empty($this->post_ids_processed[$post_ID]) ) // Processed already
    436                         return 0;
    437 
    438                 set_time_limit( 60 );
    439 
    440                 // There are only ever one of these
    441                 $post_title     = $this->get_tag( $post, 'title' );
    442                 $post_date      = $this->get_tag( $post, 'wp:post_date' );
    443                 $post_date_gmt  = $this->get_tag( $post, 'wp:post_date_gmt' );
    444                 $comment_status = $this->get_tag( $post, 'wp:comment_status' );
    445                 $ping_status    = $this->get_tag( $post, 'wp:ping_status' );
    446                 $post_status    = $this->get_tag( $post, 'wp:status' );
    447                 $post_name      = $this->get_tag( $post, 'wp:post_name' );
    448                 $post_parent    = $this->get_tag( $post, 'wp:post_parent' );
    449                 $menu_order     = $this->get_tag( $post, 'wp:menu_order' );
    450                 $post_type      = $this->get_tag( $post, 'wp:post_type' );
    451                 $post_password  = $this->get_tag( $post, 'wp:post_password' );
    452                 $is_sticky              = $this->get_tag( $post, 'wp:is_sticky' );
    453                 $guid           = $this->get_tag( $post, 'guid' );
    454                 $post_author    = $this->get_tag( $post, 'dc:creator' );
    455 
    456                 $post_excerpt = $this->get_tag( $post, 'excerpt:encoded' );
    457                 $post_excerpt = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_excerpt);
    458                 $post_excerpt = str_replace('<br>', '<br />', $post_excerpt);
    459                 $post_excerpt = str_replace('<hr>', '<hr />', $post_excerpt);
    460 
    461                 $post_content = $this->get_tag( $post, 'content:encoded' );
    462                 $post_content = preg_replace_callback('|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_content);
    463                 $post_content = str_replace('<br>', '<br />', $post_content);
    464                 $post_content = str_replace('<hr>', '<hr />', $post_content);
    465 
    466                 preg_match_all('|<category domain="tag">(.*?)</category>|is', $post, $tags);
    467                 $tags = $tags[1];
    468 
    469                 $tag_index = 0;
    470                 foreach ($tags as $tag) {
    471                         $tags[$tag_index] = $wpdb->escape( html_entity_decode( str_replace(array( '<![CDATA[', ']]>' ), '', $tag ) ) );
    472                         $tag_index++;
    473                 }
    474 
    475                 preg_match_all('|<category>(.*?)</category>|is', $post, $categories);
    476                 $categories = $categories[1];
    477 
    478                 $cat_index = 0;
    479                 foreach ($categories as $category) {
    480                         $categories[$cat_index] = $wpdb->escape( html_entity_decode( str_replace( array( '<![CDATA[', ']]>' ), '', $category ) ) );
    481                         $cat_index++;
    482                 }
    483 
    484                 $post_exists = post_exists($post_title, '', $post_date);
    485 
    486                 if ( $post_exists ) {
    487                         echo '<li>';
    488                         printf(__('Post <em>%s</em> already exists.', 'wordpress-importer'), stripslashes($post_title));
    489                         $comment_post_ID = $post_id = $post_exists;
    490                 } else {
    491 
    492                         // If it has parent, process parent first.
    493                         $post_parent = (int) $post_parent;
    494                         if ($post_parent) {
    495                                 // if we already know the parent, map it to the local ID
    496                                 if ( isset( $this->post_ids_processed[$post_parent] ) ) {
    497                                         $post_parent = $this->post_ids_processed[$post_parent];  // new ID of the parent
     390                                if ( is_wp_error( $post_id ) ) {
     391                                        echo 'Error importing post object: ' . $post_id->get_error_message() . '<br />';
     392                                        continue;
    498393                                }
    499                                 else {
    500                                         // record the parent for later
    501                                         $this->orphans[intval($post_ID)] = $post_parent;
    502                                 }
    503                         }
    504394
    505                         echo '<li>';
    506 
    507                         $post_author = $this->checkauthor($post_author); //just so that if a post already exists, new users are not created by checkauthor
    508 
    509                         $postdata = compact('post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_excerpt', 'post_title', 'post_status', 'post_name', 'comment_status', 'ping_status', 'guid', 'post_parent', 'menu_order', 'post_type', 'post_password');
    510                         $postdata['import_id'] = $post_ID;
    511                         if ($post_type == 'attachment') {
    512                                 $remote_url = $this->get_tag( $post, 'wp:attachment_url' );
    513                                 if ( !$remote_url )
    514                                         $remote_url = $guid;
    515 
    516                                 $comment_post_ID = $post_id = $this->process_attachment($postdata, $remote_url);
    517                                 if ( !$post_id or is_wp_error($post_id) )
    518                                         return $post_id;
    519                         }
    520                         else {
    521                                 printf(__('Importing post <em>%s</em>...', 'wordpress-importer') . "\n", stripslashes($post_title));
    522                                 $comment_post_ID = $post_id = wp_insert_post($postdata);
    523                                 if ( $post_id && $is_sticky == 1 )
     395                                if ( $post['is_sticky'] == 1 )
    524396                                        stick_post( $post_id );
    525 
    526397                        }
    527398
    528                         if ( is_wp_error( $post_id ) )
    529                                 return $post_id;
     399                        // map pre-import ID to local ID
     400                        $this->processed_posts[intval($post['post_id'])] = (int) $post_id;
    530401
    531                         // Memorize old and new ID.
    532                         if ( $post_id && $post_ID ) {
    533                                 $this->post_ids_processed[intval($post_ID)] = intval($post_id);
    534                         }
    535 
    536                         // Add categories.
    537                         if (count($categories) > 0) {
    538                                 $post_cats = array();
    539                                 foreach ($categories as $category) {
    540                                         if ( '' == $category )
    541                                                 continue;
    542                                         $slug = sanitize_term_field('slug', $category, 0, 'category', 'db');
    543                                         $cat = get_term_by('slug', $slug, 'category');
    544                                         $cat_ID = 0;
    545                                         if ( ! empty($cat) )
    546                                                 $cat_ID = $cat->term_id;
    547                                         if ($cat_ID == 0) {
    548                                                 $category = $wpdb->escape($category);
    549                                                 $cat_ID = wp_insert_category(array('cat_name' => $category));
    550                                                 if ( is_wp_error($cat_ID) )
     402                        // add categories, tags and other terms
     403                        if ( ! empty( $post['terms'] ) ) {
     404                                foreach ( $post['terms'] as $term ) {
     405                                        $term_exists = term_exists( $term['slug'], $term['domain'] );
     406                                        $term_id = is_array( $term_exists ) ? $term_exists['term_id'] : $term_exists;
     407                                        if ( ! $term_id ) {
     408                                                $t = wp_insert_term( $term['name'], $term['domain'], array( 'slug' => $term['slug'] ) );
     409                                                if ( ! is_wp_error( $t ) ) {
     410                                                        $term_id = $t['term_id'];
     411                                                } else {
     412                                                        echo $term['name'] . ' :: ' . $t->get_error_message();
    551413                                                        continue;
     414                                                }
    552415                                        }
    553                                         $post_cats[] = $cat_ID;
     416                                        $terms_to_set[$term['domain']][] = intval( $term_id );
    554417                                }
    555                                 wp_set_post_categories($post_id, $post_cats);
     418
     419                                foreach ( $terms_to_set as $tax => $ids ) {
     420                                        $tt_ids = wp_set_post_terms( $post_id, $ids, $tax );
     421                                }
     422                                unset( $post['terms'], $terms_to_set );
    556423                        }
    557424
    558                         // Add tags.
    559                         if (count($tags) > 0) {
    560                                 $post_tags = array();
    561                                 foreach ($tags as $tag) {
    562                                         if ( '' == $tag )
    563                                                 continue;
    564                                         $slug = sanitize_term_field('slug', $tag, 0, 'post_tag', 'db');
    565                                         $tag_obj = get_term_by('slug', $slug, 'post_tag');
    566                                         $tag_id = 0;
    567                                         if ( ! empty($tag_obj) )
    568                                                 $tag_id = $tag_obj->term_id;
    569                                         if ( $tag_id == 0 ) {
    570                                                 $tag = $wpdb->escape($tag);
    571                                                 $tag_id = wp_insert_term($tag, 'post_tag');
    572                                                 if ( is_wp_error($tag_id) )
    573                                                         continue;
    574                                                 $tag_id = $tag_id['term_id'];
     425                        // add/update comments
     426                        if ( ! empty( $post['comments'] ) ) {
     427                                $num_comments = 0;
     428                                $inserted_comments = array();
     429                                foreach ( $post['comments'] as $comment ) {
     430                                        $comment_id     = $comment['comment_id'];
     431                                        $newcomments[$comment_id]['comment_post_ID']      = $comment_post_ID;
     432                                        $newcomments[$comment_id]['comment_author']       = $comment['comment_author'];
     433                                        $newcomments[$comment_id]['comment_author_email'] = $comment['comment_author_email'];
     434                                        $newcomments[$comment_id]['comment_author_IP']    = $comment['comment_author_IP'];
     435                                        $newcomments[$comment_id]['comment_author_url']   = $comment['comment_author_url'];
     436                                        $newcomments[$comment_id]['comment_date']         = $comment['comment_date'];
     437                                        $newcomments[$comment_id]['comment_date_gmt']     = $comment['comment_date_gmt'];
     438                                        $newcomments[$comment_id]['comment_content']      = $comment['comment_content'];
     439                                        $newcomments[$comment_id]['comment_approved']     = $comment['comment_approved'];
     440                                        $newcomments[$comment_id]['comment_type']         = ! empty( $comment['comment_type'] ) ? $comment['comment_type'] : 'comment';
     441                                        $newcomments[$comment_id]['comment_parent']       = $comment['comment_parent'];
     442                                }
     443                                ksort( $newcomments );
     444
     445                                foreach ( $newcomments as $key => $comment ) {
     446                                        // if this is a new post we can skip the comment_exists() check
     447                                        if ( ! $post_exists || ! comment_exists( $comment['comment_author'], $comment['comment_date'] ) ) {
     448                                                if ( isset( $inserted_comments[$comment['comment_parent']] ) )
     449                                                        $comment['comment_parent'] = $inserted_comments[$comment['comment_parent']];
     450                                                $comment = wp_filter_comment( $comment );
     451                                                $inserted_comments[$key] = wp_insert_comment( $comment );
     452                                                $num_comments++;
    575453                                        }
    576                                         $post_tags[] = intval($tag_id);
    577454                                }
    578                                 wp_set_post_tags($post_id, $post_tags);
     455                                unset( $newcomments, $inserted_comments, $post['comments'] );
    579456                        }
    580                 }
    581457
    582                 // Now for comments
    583                 preg_match_all('|<wp:comment>(.*?)</wp:comment>|is', $post, $comments);
    584                 $comments = $comments[1];
    585                 $num_comments = 0;
    586                 $inserted_comments = array();
    587                 if ( $comments) {
    588                         foreach ($comments as $comment) {
    589                                 $comment_id     = $this->get_tag( $comment, 'wp:comment_id');
    590                                 $newcomments[$comment_id]['comment_post_ID']      = $comment_post_ID;
    591                                 $newcomments[$comment_id]['comment_author']       = $this->get_tag( $comment, 'wp:comment_author');
    592                                 $newcomments[$comment_id]['comment_author_email'] = $this->get_tag( $comment, 'wp:comment_author_email');
    593                                 $newcomments[$comment_id]['comment_author_IP']    = $this->get_tag( $comment, 'wp:comment_author_IP');
    594                                 $newcomments[$comment_id]['comment_author_url']   = $this->get_tag( $comment, 'wp:comment_author_url');
    595                                 $newcomments[$comment_id]['comment_date']         = $this->get_tag( $comment, 'wp:comment_date');
    596                                 $newcomments[$comment_id]['comment_date_gmt']     = $this->get_tag( $comment, 'wp:comment_date_gmt');
    597                                 $newcomments[$comment_id]['comment_content']      = $this->get_tag( $comment, 'wp:comment_content');
    598                                 $newcomments[$comment_id]['comment_approved']     = $this->get_tag( $comment, 'wp:comment_approved');
    599                                 $newcomments[$comment_id]['comment_type']         = $this->get_tag( $comment, 'wp:comment_type');
    600                                 $newcomments[$comment_id]['comment_parent']       = $this->get_tag( $comment, 'wp:comment_parent');
    601                         }
    602                         // Sort by comment ID, to make sure comment parents exist (if there at all)
    603                         ksort($newcomments);
    604                         foreach ($newcomments as $key => $comment) {
    605                                 // if this is a new post we can skip the comment_exists() check
    606                                 if ( !$post_exists || !comment_exists($comment['comment_author'], $comment['comment_date']) ) {
    607                                         if (isset($inserted_comments[$comment['comment_parent']]))
    608                                                 $comment['comment_parent'] = $inserted_comments[$comment['comment_parent']];
    609                                         $comment = wp_filter_comment($comment);
    610                                         $inserted_comments[$key] = wp_insert_comment($comment);
    611                                         $num_comments++;
     458                        // add/update post meta
     459                        if ( isset( $post['postmeta'] ) ) {
     460                                foreach ( $post['postmeta'] as $meta ) {
     461                                        $key = apply_filters( 'import_post_meta_key', $meta['key'] );
     462                                        if ( $key ) {
     463                                                update_post_meta( $post_id, $key, $meta['value'] );
     464                                                do_action( 'import_post_meta', $post_id, $key, $meta['value'] );
     465                                        }
    612466                                }
    613467                        }
    614468                }
    615 
    616                 if ( $num_comments )
    617                         printf(' '._n('(%s comment)', '(%s comments)', $num_comments, 'wordpress-importer'), $num_comments);
    618 
    619                 // Now for post meta
    620                 preg_match_all('|<wp:postmeta>(.*?)</wp:postmeta>|is', $post, $postmeta);
    621                 $postmeta = $postmeta[1];
    622                 if ( $postmeta) { foreach ($postmeta as $p) {
    623                         $key   = $this->get_tag( $p, 'wp:meta_key' );
    624                         $value = $this->get_tag( $p, 'wp:meta_value' );
    625 
    626                         $this->process_post_meta($post_id, $key, $value);
    627 
    628                 } }
    629 
    630                 do_action('import_post_added', $post_id);
    631                 print "</li>\n";
    632469        }
    633470
    634         function process_post_meta($post_id, $key, $value) {
    635                 // the filter can return false to skip a particular metadata key
    636                 $_key = apply_filters('import_post_meta_key', $key);
    637                 if ( $_key ) {
    638                         add_post_meta( $post_id, $_key, $value );
    639                         do_action('import_post_meta', $post_id, $_key, $value);
    640                 }
    641         }
    642 
    643         function process_attachment($postdata, $remote_url) {
    644                 if ($this->fetch_attachments and $remote_url) {
    645                         printf( __('Importing attachment <em>%s</em>... ', 'wordpress-importer'), htmlspecialchars($remote_url) );
    646 
    647                         // If the URL is absolute, but does not contain http, upload it assuming the base_site_url variable
    648                         if ( preg_match('/^\/[\w\W]+$/', $remote_url) )
    649                                 $remote_url = rtrim($this->base_url,'/').$remote_url;
    650 
    651                         $upload = $this->fetch_remote_file($postdata, $remote_url);
    652                         if ( is_wp_error($upload) ) {
    653                                 printf( __('Remote file error: %s', 'wordpress-importer'), htmlspecialchars($upload->get_error_message()) );
    654                                 return $upload;
    655                         }
    656                         else {
    657                                 print '('.size_format(filesize($upload['file'])).')';
    658                         }
    659 
    660                         if ( 0 == filesize( $upload['file'] ) ) {
    661                                 print __( "Zero length file, deleting" , 'wordpress-importer') . "\n";
    662                                 unlink( $upload['file'] );
     471        function process_menu_item( $item ) {
     472                if ( isset( $item['terms'][0]['slug'] ) ) {
     473                        $menu_id = term_exists( $item['terms'][0]['slug'], 'nav_menu' );
     474                        if ( ! $menu_id ) {
     475                                echo 'Menu item skipped due to invalid menu slug';
    663476                                return;
     477                        } else {
     478                                $menu_id = is_array( $menu_id ) ? $menu_id['term_id'] : $menu_id;
    664479                        }
     480                } else {
     481                        echo 'Menu item skipped due to missing menu slug';
     482                        return;
     483                }
    665484
    666                         if ( $info = wp_check_filetype($upload['file']) ) {
    667                                 $postdata['post_mime_type'] = $info['type'];
    668                         }
    669                         else {
    670                                 print __('Invalid file type', 'wordpress-importer');
    671                                 return;
    672                         }
     485                foreach ( $item['postmeta'] as $meta )
     486                        $$meta['key'] = $meta['value'];
    673487
    674                         $postdata['guid'] = $upload['url'];
     488                if ( 'taxonomy' == $_menu_item_type && isset( $this->processed_terms[intval($_menu_item_object_id)] ) ) {
     489                        $_menu_item_object_id = $this->processed_terms[intval($_menu_item_object_id)];
     490                } else if ( 'post_type' == $_menu_item_type && isset( $this->processed_posts[intval($_menu_item_object_id)] ) ) {
     491                        $_menu_item_object_id = $this->processed_posts[intval($_menu_item_object_id)];
     492                } else if ( 'custom' != $_menu_item_type ) {
     493                        // associated object is missing or not imported yet, we'll retry later
     494                        $this->missing_menu_items[] = $item;
     495                        return;
     496                }
    675497
    676                         // as per wp-admin/includes/upload.php
    677                         $post_id = wp_insert_attachment($postdata, $upload['file']);
    678                         wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) );
     498                if ( isset( $this->processed_menu_items[intval($_menu_item_menu_item_parent)] ) ) {
     499                        $_menu_item_menu_item_parent = $this->processed_menu_items[intval($_menu_item_menu_item_parent)];
     500                } else if ( $_menu_item_menu_item_parent ) {
     501                        $this->menu_item_orphans[intval($item['post_id'])] = (int) $_menu_item_menu_item_parent;
     502                        $_menu_item_menu_item_parent = 0;
     503                }
    679504
    680                         // remap the thumbnail url.  this isn't perfect because we're just guessing the original url.
    681                         if ( preg_match('@^image/@', $info['type']) && $thumb_url = wp_get_attachment_thumb_url($post_id) ) {
    682                                 $parts = pathinfo($remote_url);
    683                                 $ext = $parts['extension'];
    684                                 $name = basename($parts['basename'], ".{$ext}");
    685                                 $this->url_remap[$parts['dirname'] . '/' . $name . '.thumbnail.' . $ext] = $thumb_url;
    686                         }
     505                $args = array(
     506                        'menu-item-object-id' => $_menu_item_object_id,
     507                        'menu-item-object' => $_menu_item_object,
     508                        'menu-item-parent-id' => $_menu_item_menu_item_parent,
     509                        'menu-item-position' => intval( $item['menu_order'] ),
     510                        'menu-item-type' => $_menu_item_type,
     511                        'menu-item-title' => $item['post_title'],
     512                        'menu-item-url' => $_menu_item_url,
     513                        'menu-item-description' => $item['post_content'],
     514                        'menu-item-attr-title' => $item['post_excerpt'],
     515                        'menu-item-target' => $_menu_item_target,
     516                        'menu-item-classes' => $_menu_item_classes,
     517                        'menu-item-xfn' => $_menu_item_xfn,
     518                        'menu-item-status' => $item['status']
     519                );
    687520
    688                         return $post_id;
    689                 }
    690                 else {
    691                         printf( __('Skipping attachment <em>%s</em>', 'wordpress-importer'), htmlspecialchars($remote_url) );
    692                 }
     521                $id = wp_update_nav_menu_item( $menu_id, 0, $args );
     522                if ( $id && ! is_wp_error( $id ) )
     523                        $this->processed_menu_items[intval($item['post_id'])] = (int) $id;
    693524        }
     525       
     526        function process_attachment( $post, $url ) {
     527                if ( ! ( $this->fetch_attachments && $url ) )
     528                        return new WP_Error( 'attachment_processing_error',
     529                                __( 'Fetching attachments is not allowed or an empty URL was provided', 'wordpress-importer' ) );
     530                       
     531                // if the URL is absolute, but does not contain address, then upload it assuming base_site_url
     532                if ( preg_match( '|^/[\w\W]+$|', $url ) )
     533                        $url = rtrim( $this->base_url, '/' ) . $url;
     534                       
     535                $upload = $this->fetch_remote_file( $url, $post );
     536                if ( is_wp_error( $upload ) )
     537                        return $upload;
     538                       
     539                if ( $info = wp_check_filetype( $upload['file'] ) )
     540                        $post['post_mime_type'] = $info['type'];
     541                else
     542                        return new WP_Error( 'attachment_processing_error', __('Invalid file type', 'wordpress-importer') );
     543                       
     544                $post['guid'] = $upload['url'];
    694545
    695         function fetch_remote_file( $post, $url ) {
    696                 add_filter( 'http_request_timeout', array( &$this, 'bump_request_timeout' ) );
     546                // as per wp-admin/includes/upload.php
     547                $post_id = wp_insert_attachment( $post, $upload['file'] );
     548                wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) );
    697549
    698                 $upload = wp_upload_dir($post['post_date']);
     550                // remap the thumbnail url.  this isn't perfect because we're just guessing the original url.
     551                if ( preg_match( '@^image/@', $info['type'] ) && $thumb_url = wp_get_attachment_thumb_url( $post_id ) ) {
     552                        $parts = pathinfo( $url );
     553                        $ext = $parts['extension'];
     554                        $name = basename($parts['basename'], ".{$ext}");
     555                        $this->url_remap[$parts['dirname'] . '/' . $name . '.thumbnail.' . $ext] = $thumb_url;
     556                }
    699557
     558                return $post_id;       
     559        }
     560       
     561        function fetch_remote_file( $url, $post ) {
     562                add_filter( 'http_request_timeout', array( &$this, 'bump_request_timeout' ) );
     563               
    700564                // extract the file name and extension from the url
    701                 $file_name = basename($url);
    702 
    703                 // get placeholder file in the upload dir with a unique sanitized filename
    704                 $upload = wp_upload_bits( $file_name, 0, '', $post['post_date']);
    705                 if ( $upload['error'] ) {
    706                         echo $upload['error'];
     565                $file_name = basename( $url );
     566               
     567                // get placeholder file in the upload dir with a unique, sanitized filename
     568                $upload = wp_upload_bits( $file_name, 0, '', $post['post_date'] );
     569                if ( $upload['error'] )
    707570                        return new WP_Error( 'upload_dir_error', $upload['error'] );
    708                 }
    709571
    710572                // fetch the remote url and write it to the placeholder file
    711                 $headers = wp_get_http($url, $upload['file']);
     573                $headers = wp_get_http( $url, $upload['file'] );
    712574
    713                 //Request failed
     575                // request failed
    714576                if ( ! $headers ) {
    715                         @unlink($upload['file']);
     577                        @unlink( $upload['file'] );
    716578                        return new WP_Error( 'import_file_error', __('Remote server did not respond', 'wordpress-importer') );
    717579                }
    718580
    719581                // make sure the fetch was successful
    720582                if ( $headers['response'] != '200' ) {
    721                         @unlink($upload['file']);
    722                         return new WP_Error( 'import_file_error', sprintf(__('Remote file returned error response %1$d %2$s', 'wordpress-importer'), $headers['response'], get_status_header_desc($headers['response']) ) );
     583                        @unlink( $upload['file'] );
     584                        return new WP_Error( 'import_file_error', sprintf( __('Remote server returned error response %1$d %2$s', 'wordpress-importer'), $headers['response'], get_status_header_desc($headers['response']) ) );
    723585                }
    724                 elseif ( isset($headers['content-length']) && filesize($upload['file']) != $headers['content-length'] ) {
    725                         @unlink($upload['file']);
     586               
     587                $filesize = filesize( $upload['file'] );
     588               
     589                if ( isset( $headers['content-length'] ) && $filesize != $headers['content-length'] ) {
     590                        @unlink( $upload['file'] );
    726591                        return new WP_Error( 'import_file_error', __('Remote file is incorrect size', 'wordpress-importer') );
    727592                }
     593               
     594                if ( 0 == $filesize ) {
     595                        @unlink( $upload['file'] );
     596                        return new WP_Error( 'import_file_error', __('Zero size file downloaded', 'wordpress-importer') );                     
     597                }
    728598
    729                 $max_size = $this->max_attachment_size();
    730                 if ( !empty($max_size) and filesize($upload['file']) > $max_size ) {
    731                         @unlink($upload['file']);
     599                $max_size = (int) $this->max_attachment_size();
     600                if ( ! empty( $max_size ) && $filesize > $max_size ) {
     601                        @unlink( $upload['file'] );
    732602                        return new WP_Error( 'import_file_error', sprintf(__('Remote file is too large, limit is %s', size_format($max_size), 'wordpress-importer')) );
    733603                }
    734604
     
    736606                $this->url_remap[$url] = $upload['url'];
    737607                $this->url_remap[$post['guid']] = $upload['url'];
    738608                // if the remote url is redirected somewhere else, keep track of the destination too
    739                 if ( $headers['x-final-location'] != $url )
     609                if ( isset($headers['x-final-location']) && $headers['x-final-location'] != $url )
    740610                        $this->url_remap[$headers['x-final-location']] = $upload['url'];
    741611
    742                 return $upload;
    743 
     612                return $upload;         
    744613        }
    745614
    746         /**
    747          * Bump up the request timeout for http requests
    748          *
    749          * @param int $val
    750          * @return int
    751          */
    752         function bump_request_timeout( $val ) {
    753                 return 60;
    754         }
     615        function backfill_parents() {
     616                global $wpdb;
    755617
    756         // sort by strlen, longest string first
    757         function cmpr_strlen($a, $b) {
    758                 return strlen($b) - strlen($a);
     618                foreach ( $this->post_orphans as $child_id => $parent_id ) {
     619                        $local_child_id = $local_parent_id = false;
     620                        if ( isset( $this->processed_posts[$child_id] ) )
     621                                $local_child_id = $this->processed_posts[$child_id];
     622                        if ( isset( $this->processed_posts[$parent_id] ) )
     623                                $local_parent_id = $this->processed_posts[$parent_id];
     624
     625                        if ( $local_child_id && $local_parent_id )
     626                                $wpdb->update( $wpdb->posts, array( 'post_parent' => $local_parent_id ), array( 'ID' => $local_child_id ), '%d', '%d' );                               
     627                }
     628               
     629                // all other posts/terms are imported, retry menu items with missing associated object
     630                $missing_menu_items = $this->missing_menu_items;
     631                foreach ( $missing_menu_items as $item )
     632                        $this->process_menu_item( $item );
     633
     634                foreach ( $this->menu_item_orphans as $child_id => $parent_id ) {
     635                        $local_child_id = $local_parent_id = 0;
     636                        if ( isset( $this->processed_menu_items[$child_id] ) )
     637                                $local_child_id = $this->processed_menu_items[$child_id];
     638                        if ( isset( $this->processed_menu_items[$parent_id] ) )
     639                                $local_parent_id = $this->processed_menu_items[$parent_id];
     640
     641                        if ( $local_child_id && $local_parent_id )
     642                                update_post_meta( $local_child_id, '_menu_item_menu_item_parent', (int) $local_parent_id );
     643                }
    759644        }
    760645
    761         // update url references in post bodies to point to the new local files
    762646        function backfill_attachment_urls() {
    763 
     647                global $wpdb;
     648               
    764649                // make sure we do the longest urls first, in case one is a substring of another
    765                 uksort($this->url_remap, array(&$this, 'cmpr_strlen'));
     650                uksort( $this->url_remap, array(&$this, 'cmpr_strlen') );
    766651
    767                 global $wpdb;
    768                 foreach ($this->url_remap as $from_url => $to_url) {
     652                foreach ( $this->url_remap as $from_url => $to_url ) {
    769653                        // remap urls in post_content
    770                         $wpdb->query( $wpdb->prepare("UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, '%s', '%s')", $from_url, $to_url) );
     654                        $wpdb->query( $wpdb->prepare("UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url) );
    771655                        // remap enclosure urls
    772                         $result = $wpdb->query( $wpdb->prepare("UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, '%s', '%s') WHERE meta_key='enclosure'", $from_url, $to_url) );
     656                        $result = $wpdb->query( $wpdb->prepare("UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url) );
    773657                }
     658        }       
     659
     660        function parse( $file ) {
     661                $parser = new WXR_Parser();
     662                return $parser->parse( $file );
    774663        }
    775664
    776         // update the post_parent of orphans now that we know the local id's of all parents
    777         function backfill_parents() {
    778                 global $wpdb;
     665        function header() {
     666                echo '<div class="wrap">';
     667                screen_icon();
     668                echo '<h2>' . __( 'Import WordPress', 'wordpress-importer' ) . '</h2>';
     669        }
    779670
    780                 foreach ($this->orphans as $child_id => $parent_id) {
    781                         $local_child_id = $local_parent_id = false;
    782                         if ( isset( $this->post_ids_processed[$child_id] ) )
    783                                 $local_child_id = $this->post_ids_processed[$child_id];
    784                         if ( isset( $this->post_ids_processed[$parent_id] ) )
    785                                 $local_parent_id = $this->post_ids_processed[$parent_id];
     671        function footer() {
     672                echo '</div>';
     673        }
    786674
    787                         if ($local_child_id and $local_parent_id) {
    788                                 $wpdb->update($wpdb->posts, array('post_parent' => $local_parent_id), array('ID' => $local_child_id) );
    789                         }
    790                 }
     675        function greet() {
     676                echo '<div class="narrow">';
     677                echo '<p>'.__( 'Howdy! Upload your WordPress eXtended RSS (WXR) file and we&#8217;ll import the posts, pages, comments, custom fields, categories, and tags into this site.', 'wordpress-importer' ).'</p>';
     678                echo '<p>'.__( 'Choose a WXR file to upload, then click Upload file and import.', 'wordpress-importer' ).'</p>';
     679                wp_import_upload_form( 'admin.php?import=wordpress&amp;step=1' );
     680                echo '</div>';
    791681        }
    792682
    793         function is_valid_meta_key($key) {
     683        function is_valid_meta_key( $key ) {
    794684                // skip attachment metadata since we'll regenerate it from scratch
    795                 if ( $key == '_wp_attached_file' || $key == '_wp_attachment_metadata' )
     685                // skip _edit_lock and _edit_last
     686                if ( in_array( $key, array( '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock', '_edit_last' ) ) )
    796687                        return false;
    797688                return $key;
    798689        }
    799690
    800691        // give the user the option of creating new users to represent authors in the import file?
    801692        function allow_create_users() {
    802                 return apply_filters('import_allow_create_users', true);
     693                return apply_filters( 'import_allow_create_users', true );
    803694        }
    804695
    805696        // give the user the option of downloading and importing attached files
    806697        function allow_fetch_attachments() {
    807                 return apply_filters('import_allow_fetch_attachments', true);
     698                return apply_filters( 'import_allow_fetch_attachments', true );
    808699        }
    809 
     700       
     701        function bump_request_timeout() {
     702                return 60;
     703        }
     704       
    810705        function max_attachment_size() {
    811                 // can be overridden with a filter - 0 means no limit
    812                 return apply_filters('import_attachment_size_limit', 0);
     706                return apply_filters( 'import_attachment_size_limit', 0 );
    813707        }
    814 
    815         function import_start() {
    816                 wp_defer_term_counting(true);
    817                 wp_defer_comment_counting(true);
    818                 do_action('import_start');
    819         }
    820 
    821         function import_end() {
    822                 do_action('import_end');
    823 
    824                 // clear the caches after backfilling
    825                 foreach ($this->post_ids_processed as $post_id)
    826                         clean_post_cache($post_id);
    827 
    828                 wp_defer_term_counting(false);
    829                 wp_defer_comment_counting(false);
    830         }
    831 
    832         function import($id, $fetch_attachments = false) {
    833                 $this->id = (int) $id;
    834                 $this->fetch_attachments = ($this->allow_fetch_attachments() && (bool) $fetch_attachments);
    835 
    836                 add_filter('import_post_meta_key', array($this, 'is_valid_meta_key'));
    837                 $file = get_attached_file($this->id);
    838                 $this->import_file($file);
    839         }
    840 
    841         function import_file($file) {
    842                 $this->file = $file;
    843 
    844                 $this->import_start();
    845                 $this->get_authors_from_post();
    846                 wp_suspend_cache_invalidation(true);
    847                 $this->get_entries();
    848                 $this->process_categories();
    849                 $this->process_tags();
    850                 $this->process_terms();
    851                 $result = $this->process_posts();
    852                 wp_suspend_cache_invalidation(false);
    853                 $this->backfill_parents();
    854                 $this->backfill_attachment_urls();
    855                 $this->import_end();
    856 
    857                 if ( is_wp_error( $result ) )
    858                         return $result;
    859         }
    860 
    861         function handle_upload() {
    862                 $file = wp_import_handle_upload();
    863                 if ( isset($file['error']) ) {
    864                         echo '<p>'.__('Sorry, there has been an error.', 'wordpress-importer').'</p>';
    865                         echo '<p><strong>' . $file['error'] . '</strong></p>';
    866                         return false;
    867                 }
    868                 $this->file = $file['file'];
    869                 $this->id = (int) $file['id'];
    870                 return true;
    871         }
    872 
    873         function dispatch() {
    874                 if (empty ($_GET['step']))
    875                         $step = 0;
    876                 else
    877                         $step = (int) $_GET['step'];
    878 
    879                 $this->header();
    880                 switch ($step) {
    881                         case 0 :
    882                                 $this->greet();
    883                                 break;
    884                         case 1 :
    885                                 check_admin_referer('import-upload');
    886                                 if ( $this->handle_upload() )
    887                                         $this->select_authors();
    888                                 break;
    889                         case 2:
    890                                 check_admin_referer('import-wordpress');
    891                                 $fetch_attachments = ! empty( $_POST['attachments'] );
    892                                 $result = $this->import( $_GET['id'], $fetch_attachments);
    893                                 if ( is_wp_error( $result ) )
    894                                         echo $result->get_error_message();
    895                                 break;
    896                 }
    897                 $this->footer();
    898         }
    899 
    900         function WP_Import() {
    901                 // Nothing.
    902         }
     708       
     709        function cmpr_strlen( $a, $b ) {
     710                return strlen($b) - strlen($a);
     711        }       
    903712}
    904713
    905714/**