Make WordPress Core

Ticket #15197: 15197-import.002.diff

File 15197-import.002.diff, 76.8 KB (added by duck_, 15 years ago)
  • 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 */
     183class WXR_Parser_XML {
     184        var $wp_tags = array(
     185                'wp:post_id', 'wp:post_date', 'wp:post_date_gmt', 'wp:comment_status', 'wp:ping_status',
     186                'wp:status', 'wp:post_name', 'wp:post_parent', 'wp:menu_order', 'wp:post_type', 'wp:post_password',
     187                'wp:is_sticky', 'wp:term_id', 'wp:category_nicename', 'wp:category_parent', 'wp:cat_name', 'wp:category_description',
     188                'wp:tag_slug', 'wp:tag_name', 'wp:tag_description', 'wp:term_taxonomy', 'wp:term_parent',
     189                'wp:term_name', 'wp:term_description', 'wp:author_login', 'wp:author_email', 'wp:author_display_name',
     190                'wp:author_first_name', 'wp:author_last_name',
     191        );
     192        var $wp_sub_tags = array(
     193                'wp:comment_id', 'wp:comment_author', 'wp:comment_author_email', 'wp:comment_author_url',
     194                'wp:comment_author_IP', 'wp:comment_date', 'wp:comment_date_gmt', 'wp:comment_content',
     195                'wp:comment_approved', 'wp:comment_type', 'wp:comment_parent', 'wp:comment_user_id',
     196        );
     197
     198        function parse( $file ) {
     199                $this->is_wxr_file = $this->in_post = $this->cdata = $this->data = $this->sub_data = $this->in_tag = $this->in_sub_tag = false;
     200                $this->authors = $this->posts = $this->term = $this->category = $this->tag = array();
     201
     202                $xml = xml_parser_create( 'UTF-8' );
     203                xml_parser_set_option( $xml, XML_OPTION_SKIP_WHITE, 1 );
     204                xml_parser_set_option( $xml, XML_OPTION_CASE_FOLDING, 0 );
     205                xml_set_object( $xml, $this );
     206                xml_set_character_data_handler( $xml, 'cdata' );
     207                xml_set_element_handler( $xml, 'tag_open', 'tag_close' );
     208
     209                if ( ! xml_parse( $xml, file_get_contents( $file ), true ) ) {
     210                        $error_code = xml_get_error_code( $xml );
     211                        $error_string = xml_error_string( $error_code );
     212                        return new WP_Error( 'WXR_parse_error', 'There was an error when reading this WXR file', array( $error_code, $error_string ) );
     213                }
     214                xml_parser_free( $xml );
     215
     216                if ( ! $this->is_wxr_file )
     217                        return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) );
     218
     219                return array(
     220                        'authors' => $this->authors,
     221                        'posts' => $this->posts,
     222                        'categories' => $this->category,
     223                        'tags' => $this->tag,
     224                        'terms' => $this->term,
     225                        'base_url' => $this->base_url
     226                );
     227        }
     228
     229        function tag_open( $parse, $tag, $attr ) {
     230                if ( in_array( $tag, $this->wp_tags ) ) {
     231                        $this->in_tag = substr( $tag, 3 );
     232                        return;
     233                }
     234
     235                if ( in_array( $tag, $this->wp_sub_tags ) ) {
     236                        $this->in_sub_tag = substr( $tag, 3 );
     237                        return;
     238                }
     239
     240                switch ( $tag ) {
     241                        case 'category':
     242                                if ( isset($attr['domain'], $attr['nicename']) ) {
     243                                        $this->sub_data['domain'] = $attr['domain'];
     244                                        $this->sub_data['slug'] = $attr['nicename'];
     245                                }
     246                                break;
     247                        case 'item': $this->in_post = true;
     248                        case 'title': if ( $this->in_post ) $this->in_tag = 'post_title'; break;
     249                        case 'guid': $this->in_tag = 'guid'; break;
     250                        case 'dc:creator': $this->in_tag = 'post_author'; break;
     251                        case 'content:encoded': $this->in_tag = 'post_content'; break;
     252                        case 'excerpt:encoded': $this->in_tag = 'post_excerpt'; break;
     253
     254                        case 'wp:term_slug': $this->in_tag = 'slug'; break;
     255                        case 'wp:meta_key': $this->in_sub_tag = 'key'; break;
     256                        case 'wp:meta_value': $this->in_sub_tag = 'value'; break;
     257                }
     258        }
     259
     260        function cdata( $parser, $cdata ) {
     261                if ( ! trim( $cdata ) )
     262                        return;
     263
     264                $this->cdata .= trim( $cdata );
     265        }
     266
     267        function tag_close( $parser, $tag ) {
     268                switch ( $tag ) {
     269                        case 'wp:comment':
     270                                if ( ! empty( $this->sub_data ) )
     271                                        $this->data['comments'][] = $this->sub_data;
     272                                $this->sub_data = false;
     273                                break;
     274                        case 'category':
     275                                if ( ! empty( $this->sub_data ) ) {
     276                                        $this->sub_data['name'] = $this->cdata;
     277                                        $this->data['terms'][] = $this->sub_data;
     278                                }
     279                                $this->sub_data = false;
     280                                break;
     281                        case 'wp:postmeta':
     282                                if ( ! empty( $this->sub_data ) )
     283                                        $this->data['postmeta'][] = $this->sub_data;
     284                                $this->sub_data = false;
     285                                break;
     286                        case 'item':
     287                                $this->posts[] = $this->data;
     288                                $this->data = false;
     289                                break;
     290                        case 'wp:category':
     291                        case 'wp:tag':
     292                        case 'wp:term':
     293                                $n = substr( $tag, 3 );
     294                                array_push( $this->$n, $this->data );
     295                                $this->data = false;
     296                                break;
     297                        case 'wp:author':
     298                                if ( ! empty($this->data['author_login']) )
     299                                        $this->authors[$this->data['author_login']] = $this->data;
     300                                $this->data = false;
     301                                break;
     302                        case 'wp:base_site_url':
     303                                $this->base_url = $this->cdata;
     304                                break;
     305                        case 'wp:wxr_version':
     306                                $this->is_wxr_file = preg_match( '/\d+\.\d+/', $this->cdata );
     307                                break;
     308
     309                        default:
     310                                if ( $this->in_sub_tag ) {
     311                                        $this->sub_data[$this->in_sub_tag] = ! empty( $this->cdata ) ? $this->cdata : '';
     312                                        $this->in_sub_tag = false;
     313                                } else if ( $this->in_tag ) {
     314                                        $this->data[$this->in_tag] = ! empty( $this->cdata ) ? $this->cdata : '';
     315                                        $this->in_tag = false;
     316                                }
     317                }
     318
     319                $this->cdata = false;
     320        }
     321}
     322
     323/**
     324 * WXR Parser that uses regular expressions. Fallback for installs without an XML parser.
     325 */
     326class WXR_Parser_Regex {
     327        function WXR_Parser_Regex() {
     328                $this->__construct();
     329        }
     330
     331        function __construct() {
     332                $this->has_gzip = is_callable( 'gzopen' );
     333        }
     334
     335        function parse( $file ) {
     336                $is_wxr = $in_post = false;
     337
     338                $fp = $this->fopen( $file, 'r' );
     339                if ( $fp ) {
     340                        while ( ! $this->feof( $fp ) ) {
     341                                $importline = rtrim( $this->fgets( $fp ) );
     342
     343                                if ( ! $is_wxr && preg_match( '|<wp:wxr_version>\d+\.\d+</wp:wxr_version>|', $importline ) )
     344                                        $is_wxr = true;
     345
     346                                if ( false !== strpos( $importline, '<wp:base_site_url>' ) ) {
     347                                        preg_match( '|<wp:base_site_url>(.*?)</wp:base_site_url>|is', $importline, $url );
     348                                        $this->base_url = $url[1];
     349                                        continue;
     350                                }
     351                                if ( false !== strpos( $importline, '<wp:category>' ) ) {
     352                                        preg_match( '|<wp:category>(.*?)</wp:category>|is', $importline, $category );
     353                                        $this->categories[] = $this->process_category( $category[1] );
     354                                        continue;
     355                                }
     356                                if ( false !== strpos( $importline, '<wp:tag>' ) ) {
     357                                        preg_match( '|<wp:tag>(.*?)</wp:tag>|is', $importline, $tag );
     358                                        $this->tags[] = $this->process_tag( $tag[1] );
     359                                        continue;
     360                                }
     361                                if ( false !== strpos( $importline, '<wp:term>' ) ) {
     362                                        preg_match( '|<wp:term>(.*?)</wp:term>|is', $importline, $term );
     363                                        $this->terms[] = $this->process_term( $term[1] );
     364                                        continue;
     365                                }
     366                                if ( false !== strpos( $importline, '<wp:author>' ) ) {
     367                                        preg_match( '|<wp:author>(.*?)</wp:author>|is', $importline, $author );
     368                                        $a = $this->process_author( $author[1] );
     369                                        $this->authors[$a['author_login']] = $a;
     370                                        continue;
     371                                }
     372                                if ( false !== strpos( $importline, '<item>' ) ) {
     373                                        $post = '';
     374                                        $in_post = true;
     375                                        continue;
     376                                }
     377                                if ( false !== strpos( $importline, '</item>' ) ) {
     378                                        $in_post = false;
     379                                        $this->posts[] = $this->process_post( $post );
     380                                        continue;
     381                                }
     382                                if ( $in_post ) {
     383                                        $post .= $importline . "\n";
     384                                }
     385                        }
     386
     387                        $this->fclose($fp);
     388                }
     389
     390                if ( ! $is_wxr )
     391                        return new WP_Error( 'WXR_parse_error', __( 'This does not appear to be a WXR file, missing/invalid WXR version number', 'wordpress-importer' ) );
     392
     393                return array(
     394                        'authors' => $this->authors,
     395                        'posts' => $this->posts,
     396                        'categories' => $this->categories,
     397                        'tags' => $this->tags,
     398                        'terms' => $this->terms,
     399                        'base_url' => $this->base_url
     400                );
     401        }
     402
     403        function get_tag( $string, $tag ) {
     404                global $wpdb;
     405                preg_match( "|<$tag.*?>(.*?)</$tag>|is", $string, $return );
     406                if ( isset( $return[1] ) ) {
     407                        $return = preg_replace( '|^<!\[CDATA\[(.*)\]\]>$|s', '$1', $return[1] );
     408                        $return = $wpdb->escape( trim( $return ) );
     409                } else {
     410                        $return = '';
     411                }
     412                return $return;
     413        }
     414
     415        function process_category( $c ) {
     416                return array(
     417                        'term_id' => $this->get_tag( $c, 'wp:term_id' ),
     418                        'cat_name' => $this->get_tag( $c, 'wp:cat_name' ),
     419                        'category_nicename'     => $this->get_tag( $c, 'wp:category_nicename' ),
     420                        'category_parent' => $this->get_tag( $c, 'wp:category_parent' ),
     421                        'category_description' => $this->get_tag( $c, 'wp:category_description' ),
     422                );
     423        }
     424
     425        function process_tag( $t ) {
     426                return array(
     427                        'term_id' => $this->get_tag( $t, 'wp:term_id' ),
     428                        'tag_name' => $this->get_tag( $t, 'wp:tag_name' ),
     429                        'tag_slug' => $this->get_tag( $t, 'wp:tag_slug' ),
     430                        'tag_description' => $this->get_tag( $t, 'wp:tag_description' ),
     431                );
     432        }
     433
     434        function process_term( $t ) {
     435                return array(
     436                        'term_id' => $this->get_tag( $t, 'wp:term_id' ),
     437                        'term_taxonomy' => $this->get_tag( $t, 'wp:term_taxonomy' ),
     438                        'slug' => $this->get_tag( $t, 'wp:term_slug' ),
     439                        'term_parent' => $this->get_tag( $t, 'wp:term_parent' ),
     440                        'term_name' => $this->get_tag( $t, 'wp:term_name' ),
     441                        'term_description' => $this->get_tag( $t, 'wp:term_description' ),
     442                );
     443        }
     444
     445        function process_author( $a ) {
     446                return array(
     447                        'author_login' => $this->get_tag( $a, 'wp:author_login' ),
     448                        'author_email' => $this->get_tag( $a, 'wp:author_email' ),
     449                        'author_display_name' => $this->get_tag( $a, 'wp:author_display_name' ),
     450                        'author_first_name' => $this->get_tag( $a, 'wp:author_first_name' ),
     451                        'author_last_name' => $this->get_tag( $a, 'wp:author_last_name' ),
     452                );
     453        }
     454
     455        function process_post( $post ) {
     456                $post_id        = $this->get_tag( $post, 'wp:post_id' );
     457                $post_title     = $this->get_tag( $post, 'title' );
     458                $post_date      = $this->get_tag( $post, 'wp:post_date' );
     459                $post_date_gmt  = $this->get_tag( $post, 'wp:post_date_gmt' );
     460                $comment_status = $this->get_tag( $post, 'wp:comment_status' );
     461                $ping_status    = $this->get_tag( $post, 'wp:ping_status' );
     462                $status         = $this->get_tag( $post, 'wp:status' );
     463                $post_name      = $this->get_tag( $post, 'wp:post_name' );
     464                $post_parent    = $this->get_tag( $post, 'wp:post_parent' );
     465                $menu_order     = $this->get_tag( $post, 'wp:menu_order' );
     466                $post_type      = $this->get_tag( $post, 'wp:post_type' );
     467                $post_password  = $this->get_tag( $post, 'wp:post_password' );
     468                $is_sticky              = $this->get_tag( $post, 'wp:is_sticky' );
     469                $guid           = $this->get_tag( $post, 'guid' );
     470                $post_author    = $this->get_tag( $post, 'dc:creator' );
     471
     472                $post_excerpt = $this->get_tag( $post, 'excerpt:encoded' );
     473                $post_excerpt = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_excerpt );
     474                $post_excerpt = str_replace( '<br>', '<br />', $post_excerpt );
     475                $post_excerpt = str_replace( '<hr>', '<hr />', $post_excerpt );
     476
     477                $post_content = $this->get_tag( $post, 'content:encoded' );
     478                $post_content = preg_replace_callback( '|<(/?[A-Z]+)|', array( &$this, '_normalize_tag' ), $post_content );
     479                $post_content = str_replace( '<br>', '<br />', $post_content );
     480                $post_content = str_replace( '<hr>', '<hr />', $post_content );
     481
     482                $postdata = compact( 'post_id', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_excerpt',
     483                        'post_title', 'status', 'post_name', 'comment_status', 'ping_status', 'guid', 'post_parent',
     484                        'menu_order', 'post_type', 'post_password', 'is_sticky'
     485                );
     486
     487                preg_match_all( '|<category domain="([^"]+?)" nicename="([^"]+?)">(.+?)</category>|is', $post, $terms, PREG_SET_ORDER );
     488                foreach ( $terms as $t ) {
     489                        $post_terms[] = array(
     490                                'slug' => $t[2],
     491                                'domain' => $t[1],
     492                                'name' => str_replace( array( '<![CDATA[', ']]>' ), '', $t[3] ),
     493                        );
     494                }
     495                if ( ! empty( $post_terms ) ) $postdata['terms'] = $post_terms;
     496
     497                preg_match_all( '|<wp:comment>(.+?)</wp:comment>|is', $post, $comments );
     498                $comments = $comments[1];
     499                if ( $comments ) {
     500                        foreach ( $comments as $comment ) {
     501                                $post_comments[] = array(
     502                                        'comment_id' => $this->get_tag( $comment, 'wp:comment_id' ),
     503                                        'comment_author' => $this->get_tag( $comment, 'wp:comment_author' ),
     504                                        'comment_author_email' => $this->get_tag( $comment, 'wp:comment_author_email' ),
     505                                        'comment_author_IP' => $this->get_tag( $comment, 'wp:comment_author_IP' ),
     506                                        'comment_author_url' => $this->get_tag( $comment, 'wp:comment_author_url' ),
     507                                        'comment_date' => $this->get_tag( $comment, 'wp:comment_date' ),
     508                                        'comment_date_gmt' => $this->get_tag( $comment, 'wp:comment_date_gmt' ),
     509                                        'comment_content' => $this->get_tag( $comment, 'wp:comment_content' ),
     510                                        'comment_approved' => $this->get_tag( $comment, 'wp:comment_approved' ),
     511                                        'comment_type' => $this->get_tag( $comment, 'wp:comment_type' ),
     512                                        'comment_parent' => $this->get_tag( $comment, 'wp:comment_parent' ),
     513                                );
     514                        }
     515                }
     516                if ( ! empty( $post_comments ) ) $postdata['comments'] = $post_comments;
     517
     518                preg_match_all( '|<wp:postmeta>(.+?)</wp:postmeta>|is', $post, $postmeta );
     519                $postmeta = $postmeta[1];
     520                if ( $postmeta ) {
     521                        foreach ( $postmeta as $p ) {
     522                                $post_postmeta[] = array(
     523                                        'key' => $this->get_tag( $p, 'wp:meta_key' ),
     524                                        'value' => $this->get_tag( $p, 'wp:meta_value' ),
     525                                );
     526                        }
     527                }
     528                if ( ! empty( $post_postmeta ) ) $postdata['postmeta'] = $post_postmeta;
     529
     530                return $postdata;
     531        }
     532
     533        function _normalize_tag( $matches ) {
     534                return '<' . strtolower( $matches[1] );
     535        }
     536
     537        function fopen( $filename, $mode = 'r' ) {
     538                if ( $this->has_gzip )
     539                        return gzopen( $filename, $mode );
     540                return fopen( $filename, $mode );
     541        }
     542
     543        function feof( $fp ) {
     544                if ( $this->has_gzip )
     545                        return gzeof( $fp );
     546                return feof( $fp );
     547        }
     548
     549        function fgets( $fp, $len = 8192 ) {
     550                if ( $this->has_gzip )
     551                        return gzgets( $fp, $len );
     552                return fgets( $fp, $len );
     553        }
     554
     555        function fclose( $fp ) {
     556                if ( $this->has_gzip )
     557                        return gzclose( $fp );
     558                return fclose( $fp );
     559        }
     560}
  • 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
     41* `import_attachment_size_limit`: return an integer value for the maximum file size in bytes to save (default is 0, which is unlimited)
  • 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
     52        var $authors_from_posts = false;
    4953        var $fetch_attachments = false;
    50         var $url_remap = array ();
     54        var $url_remap = array();
    5155
    52         function header() {
    53                 echo '<div class="wrap">';
    54                 screen_icon();
    55                 echo '<h2>'.__('Import WordPress', 'wordpress-importer').'</h2>';
    56         }
     56        function WP_Import() { /* nothing */ }
    5757
    58         function footer() {
    59                 echo '</div>';
    60         }
     58        function dispatch() {
     59                $this->header();
    6160
    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         }
    69 
    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 = '';
     61                $step = empty( $_GET['step'] ) ? 0 : (int) $_GET['step'];
     62                switch ( $step ) {
     63                        case 0:
     64                                $this->greet();
     65                                break;
     66                        case 1:
     67                                check_admin_referer( 'import-upload' );
     68                                if ( $this->handle_upload() )
     69                                        $this->import_options();
     70                                break;
     71                        case 2:
     72                                check_admin_referer( 'import-wordpress' );
     73                                $this->fetch_attachments = ( ! empty( $_POST['fetch_attachments'] ) && $this->allow_fetch_attachments() );
     74                                $this->id = (int) $_POST['import_id'];
     75                                $file = get_attached_file( $this->id );
     76                                $this->import( $file );
     77                                break;
    7878                }
    79                 return $return;
    80         }
    8179
    82         function has_gzip() {
    83                 return is_callable('gzopen');
     80                $this->footer();
    8481        }
    8582
    86         function fopen($filename, $mode='r') {
    87                 if ( $this->has_gzip() )
    88                         return gzopen($filename, $mode);
    89                 return fopen($filename, $mode);
    90         }
     83        function import( $file ) {
     84                add_filter( 'import_post_meta_key', array( $this, 'is_valid_meta_key' ) );
    9185
    92         function feof($fp) {
    93                 if ( $this->has_gzip() )
    94                         return gzeof($fp);
    95                 return feof($fp);
    96         }
     86                $this->import_start( $file );
    9787
    98         function fgets($fp, $len=8192) {
    99                 if ( $this->has_gzip() )
    100                         return gzgets($fp, $len);
    101                 return fgets($fp, $len);
    102         }
     88                $this->get_author_mapping();
    10389
    104         function fclose($fp) {
    105                 if ( $this->has_gzip() )
    106                         return gzclose($fp);
    107                 return fclose($fp);
    108         }
     90                wp_suspend_cache_invalidation( true );
     91                $this->process_categories();
     92                $this->process_tags();
     93                $this->process_terms();
     94                $this->process_posts();
     95                wp_suspend_cache_invalidation( false );
    10996
    110         function get_entries($process_post_func=NULL) {
    111                 set_magic_quotes_runtime(0);
     97                // update items with missing/incorrect parent IDs
     98                $this->backfill_parents();
     99                // update attachment references within posts and postmeta
     100                $this->backfill_attachment_urls();
    112101
    113                 $doing_entry = false;
    114                 $is_wxr_file = false;
     102                $this->import_end();
     103        }
    115104
    116                 $fp = $this->fopen($this->file, 'r');
    117                 if ($fp) {
    118                         while ( !$this->feof($fp) ) {
    119                                 $importline = rtrim($this->fgets($fp));
     105        function import_start( $file ) {
     106                $import_arr = $this->parse( $file );
    120107
    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);
     108                if ( is_wp_error( $import_arr ) ) {
     109                        echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong></p>';
     110                        echo '<p>' . esc_html( $import_arr->get_error_message() ) . '</p>';
     111                        $this->footer();
     112                        die();
    167113                }
    168114
    169                 return $is_wxr_file;
     115                $this->get_authors_from_import( $import_arr );
     116                $this->posts = $import_arr['posts'];
     117                $this->terms = $import_arr['terms'];
     118                $this->categories = $import_arr['categories'];
     119                $this->tags = $import_arr['tags'];
     120                $this->base_url = esc_url( $import_arr['base_url'] );
    170121
     122                wp_defer_term_counting( true );
     123                wp_defer_comment_counting( true );
     124
     125                do_action( 'import_start' );
    171126        }
    172127
    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);
     128        function import_end() {
     129                wp_import_cleanup( $this->id );
     130
     131                wp_cache_flush();
     132                foreach ( get_taxonomies() as $tax ) {
     133                        delete_option( "{$tax}_children" );
     134                        _get_term_hierarchy( $tax );
    182135                }
    183136
    184                 return $authors;
     137                wp_defer_term_counting( false );
     138                wp_defer_comment_counting( false );
     139
     140                echo '<p>' . __( 'All done.' ) . ' <a href="' . admin_url() . '">' . __( 'Have fun!' ) . '</a>' . '</p>';
     141
     142                do_action( 'import_end' );
    185143        }
    186144
    187         function get_authors_from_post() {
    188                 global $current_user;
     145        function handle_upload() {
     146                $file = wp_import_handle_upload();
    189147
    190                 // this will populate $this->author_ids with a list of author_names => user_ids
     148                if ( isset( $file['error'] ) ) {
     149                        echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong></p>';
     150                        echo '<p>' . esc_html( $file['error'] ) . '</p>';
     151                        return false;
     152                }
    191153
    192                 foreach ( (array) $_POST['author_in'] as $i => $in_author_name ) {
     154                $this->id = (int) $file['id'];
     155                $import_data = $this->parse( $file['file'] );
     156                if ( is_wp_error( $import_data ) ) {
     157                        echo '<p><strong>' . __( 'Sorry, there has been an error.', 'wordpress-importer' ) . '</strong></p>';
     158                        echo '<p>' . esc_html( $import_data->get_error_message() ) . '</p>';
     159                        return false;
     160                }
    193161
    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
     162                $this->get_authors_from_import( $import_data );
    202163
    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;
     164                return true;
     165        }
    207166
    208                                 $user_id = username_exists($new_author_name);
    209                                 if ( !$user_id ) {
    210                                         $user_id = wp_create_user($new_author_name, wp_generate_password());
     167        function get_authors_from_import( $import_data ) {
     168                if ( ! empty( $import_data['authors'] ) ) {
     169                        $this->authors = $import_data['authors'];
     170                // no author information, grab it from the posts
     171                } else {
     172                        foreach ( $import_data['posts'] as $post ) {
     173                                $login = sanitize_user( $post['post_author'], true );
     174                                if ( empty( $login ) ) {
     175                                        _e( sprintf( 'Error importing author %s their posts will be attributed to the current user', esc_html( $post['post_author'] ) ) );
     176                                        echo '<br />';
     177                                        continue;
    211178                                }
    212179
    213                                 if ( !is_wp_error( $user_id ) ) {
    214                                         $this->author_ids[$in_author_name] = $user_id;
    215                                 }
     180                                if ( ! isset($this->authors[$login]) )
     181                                        $this->authors[$login] = array(
     182                                                'author_login' => $login,
     183                                                'author_display_name' => $post['post_author']
     184                                        );
    216185                        }
    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                         }
     186                        $this->authors_from_posts = true;
    222187                }
    223 
    224188        }
    225189
    226         function wp_authors_form() {
     190        function import_options() {
     191                $j = 0;
    227192?>
    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         }
     193<form action="<?php echo admin_url( 'admin.php?import=wordpress&amp;step=2' ); ?>" method="post">
     194        <?php wp_nonce_field( 'import-wordpress' ); ?>
     195        <input type="hidden" name="import_id" value="<?php echo $this->id; ?>" />
    234196
     197<?php if ( ! empty( $this->authors ) ) : ?>
     198        <h3><?php _e('Assign Authors', 'wordpress-importer'); ?></h3>
     199        <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>
     200<?php if ( ! $this->authors_from_posts && $this->allow_create_users() ) : ?>
     201        <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>
     202<?php endif; ?>
     203        <ol id="authors">
     204<?php foreach ( $this->authors as $author ) : ?>
     205                <li><?php $this->author_select( $j++, $author ); ?></li>
     206<?php endforeach; ?>
     207        </ol>
     208<?php endif; ?>
    235209
    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">
     210<?php if ( $this->allow_fetch_attachments() ) : ?>
     211        <h3><?php _e('Import Attachments', 'wordpress-importer'); ?></h3>
     212        <p>
     213                <input type="checkbox" value="1" name="fetch_attachments" id="import-attachments" />
     214                <label for="import-attachments"><?php _e( 'Download and import file attachments', 'wordpress-importer' ); ?></label>
     215        </p>
     216<?php endif; ?>
     217
     218        <p class="submit"><input type="submit" class="button" value="<?php esc_attr_e( 'Submit', 'wordpress-importer' ); ?>" /></p>
     219</form>
    241220<?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                 }
     221        }
    249222
    250                 if ( $this->allow_fetch_attachments() ) {
     223        function author_select( $n, $author ) {
     224                if ( ! $this->authors_from_posts && $this->allow_create_users() )
     225                        printf( __( 'Import author %1$s or map to existing user', 'wordpress-importer' ), '<strong>' . esc_html( $author['author_display_name'] ) . '</strong>' );
     226                else
     227                        printf( __( 'Map author %1$s to existing user', 'wordpress-importer' ), '<strong>' . esc_html( $author['author_display_name'] ) . '</strong>' );
    251228?>
    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 
     229                <input type="hidden" name="imported_authors[<?php echo $n; ?>]" value="<?php esc_attr_e( $author['author_login'] ); ?>" />
     230                <?php wp_dropdown_users( array( 'name' => "user_map[$n]", 'multi' => true, 'show_option_all' => __( '- Select -', 'wordpress-importer' ) ) ); ?>
    259231<?php
    260                 }
    261 
    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>';
    266 
    267232        }
    268233
    269         function users_form($n, $author) {
     234        function get_author_mapping() {
     235                if ( ! isset( $_POST['imported_authors'] ) )
     236                        return;
    270237
    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                 }
     238                foreach ( (array) $_POST['imported_authors'] as $i => $login ) {
     239                        $bad_login = $login;
     240                        $login = sanitize_user( $login, true );
    277241
    278                 // keep track of $n => $author name
    279                 echo '<input type="hidden" name="author_in['.intval($n).']" value="' . esc_attr($author).'" />';
     242                        if ( ! empty( $_POST['user_map'][$i] ) ) {
     243                                $user = get_userdata( intval($_POST['user_map'][$i]) );
     244                                if ( isset( $user->ID ) )
     245                                        $this->processed_authors[$login] = $user->ID;
     246                        } else if ( ! $this->authors_from_posts && $this->allow_create_users() ) {
     247                                $user_id = username_exists( $login );
     248                                if ( ! $user_id ) {
     249                                        $user_data = array(
     250                                                'user_login' => $login,
     251                                                'user_pass' => wp_generate_password(),
     252                                                'user_email' => $this->authors[$login]['author_email'],
     253                                                'display_name' => $this->authors[$login]['author_display_name'],
     254                                                'first_name' => $this->authors[$login]['author_first_name'],
     255                                                'last_name' => $this->authors[$login]['author_last_name'],
     256                                        );
     257                                        $user_id = wp_insert_user( $user_data );
     258                                }
    280259
    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         }
     260                                if ( ! is_wp_error( $user_id ) )
     261                                        $this->processed_authors[$login] = $user_id;
     262                                else
     263                                        _e( sprintf( 'Error importing author %s their posts will be attributed to the current user', esc_html( $post['post_author'] ) ) );
     264                        }
    292265
    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();
     266                        // failsafe: if the user_id was invalid, default to the current user
     267                        if ( empty( $this->processed_authors[$login] ) )
     268                                $this->processed_authors[$login] = (int) get_current_user_id();
    297269                }
    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                 }
    302270        }
    303271
    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 
    317272        function process_categories() {
    318                 global $wpdb;
     273                if ( empty( $this->categories ) )
     274                        return;
    319275
    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) )
     276                foreach ( $this->categories as $cat ) {
     277                        // if the category already exists leave it alone
     278                        $term_id = term_exists( $cat['category_nicename'], 'category' );
     279                        if ( $term_id ) {
     280                                if ( is_array($term_id) ) $term_id = $term_id['term_id'];
     281                                $this->processed_terms[intval($cat['term_id'])] = (int) $term_id;
    327282                                continue;
     283                        }
    328284
    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' );
     285                        $category_parent = empty( $cat['category_parent'] ) ? 0 : category_exists( $cat['category_parent'] );
     286                        $category_description = isset( $cat['category_description'] ) ? $cat['category_description'] : '';
     287                        $catarr = array(
     288                                'category_nicename' => $cat['category_nicename'],
     289                                'category_parent' => $category_parent,
     290                                'cat_name' => $cat['cat_name'],
     291                                'category_description' => $category_description
     292                        );
    333293
    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);
     294                        $id = wp_insert_category( $catarr );
     295                        if ( ! is_wp_error( $id ) ) {
     296                                $this->processed_terms[intval($cat['term_id'])] = $id;
     297                        } else {
     298                                echo __( 'Error importing category:', 'wordpress-importer' ) . ' ' . esc_html( $id->get_error_message() ) . '<br />';
     299                                continue;
     300                        }
    345301                }
    346302        }
    347303
    348304        function process_tags() {
    349                 global $wpdb;
     305                if ( empty( $this->tags ) )
     306                        return;
    350307
    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) )
     308                foreach ( $this->tags as $tag ) {
     309                        // if the tag already exists leave it alone
     310                        $term_id = term_exists( $tag['tag_slug'], 'post_tag' );
     311                        if ( $term_id ) {
     312                                if ( is_array($term_id) ) $term_id = $term_id['term_id'];
     313                                $this->processed_terms[intval($tag['term_id'])] = (int) $term_id;
    358314                                continue;
     315                        }
    359316
    360                         $slug = $this->get_tag( $c, 'wp:tag_slug' );
    361                         $description = $this->get_tag( $c, 'wp:tag_description' );
     317                        $tag_desc = isset( $tag['tag_description'] ) ? $tag['tag_description'] : '';
     318                        $tagarr = array( 'slug' => $tag['tag_slug'], 'description' => $tag_desc );
    362319
    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);
     320                        $id = wp_insert_term( $tag['tag_name'], 'post_tag', $tagarr );
     321                        if ( ! is_wp_error( $id ) ) {
     322                                $this->processed_terms[intval($tag['term_id'])] = $id['term_id'];
     323                        } else {
     324                                echo __( 'Error importing post tag:', 'wordpress-importer' ) . ' ' . esc_html( $id->get_error_message() ) . '<br />';
     325                                continue;
     326                        }
    367327                }
    368328        }
    369329
    370330        function process_terms() {
    371                 global $wpdb, $wp_taxonomies;
     331                if ( empty( $this->terms ) )
     332                        return;
    372333
    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'] );
     334                foreach ( $this->terms as $term ) {
     335                        // if the term already exists in the correct taxonomy leave it alone
     336                        $term_id = term_exists( $term['slug'], $term['term_taxonomy'] );
     337                        if ( $term_id ) {
     338                                if ( is_array($term_id) ) $term_id = $term_id['term_id'];
     339                                $this->processed_terms[intval($term['term_id'])] = (int) $term_id;
     340                                continue;
     341                        }
    378342
    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) );
     343                        if ( empty( $term['term_parent'] ) ) {
     344                                $parent = 0;
    385345                        } else {
    386                                 $taxonomies[$term->taxonomy] = array($term->name);
     346                                $parent = term_exists( $term['term_parent'], $term['term_taxonomy'] );
     347                                if ( is_array( $parent ) ) $parent = $parent['term_id'];
    387348                        }
    388                 }
     349                        $description = isset( $term['term_description'] ) ? $term['term_description'] : '';
     350                        $termarr = array( 'slug' => $term['slug'], 'description' => $description, 'parent' => intval($parent) );
    389351
    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] ) )
     352                        $id = wp_insert_term( $term['term_name'], $term['term_taxonomy'], $termarr );
     353                        if ( ! is_wp_error( $id ) ) {
     354                                $this->processed_terms[intval($term['term_id'])] = $id['term_id'];
     355                        } else {
     356                                echo __( 'Error importing term:', 'wordpress-importer' ) . ' ' . esc_html( $id->get_error_message() ) . '<br />';
    396357                                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);
     358                        }
    405359                }
    406360        }
    407361
    408         function process_author($post) {
    409                 $author = $this->get_tag( $post, 'dc:creator' );
    410                 if ($author)
    411                         $this->allauthornames[] = $author;
    412         }
    413 
    414362        function process_posts() {
    415                 echo '<ol>';
     363                foreach ( $this->posts as $post ) {
     364                        if ( isset( $this->processed_posts[$post['post_id']] ) )
     365                                continue;
    416366
    417                 $this->get_entries(array(&$this, 'process_post'));
     367                        if ( 'nav_menu_item' == $post['post_type'] ) {
     368                                $this->process_menu_item( $post );
     369                                continue;
     370                        }
    418371
    419                 echo '</ol>';
     372                        $post_exists = post_exists( $post['post_title'], '', $post['post_date'] );
     373                        if ( $post_exists ) {
     374                                $comment_post_ID = $post_id = $post_exists;
     375                        } else {
     376                                $post_parent = (int) $post['post_parent'];
     377                                if ( $post_parent ) {
     378                                        // if we already know the parent, map it to the new local ID
     379                                        if ( isset( $this->processed_posts[$post_parent] ) ) {
     380                                                $post_parent = $this->processed_posts[$post_parent];
     381                                        // otherwise record the parent for later
     382                                        } else {
     383                                                $this->post_orphans[intval($post['post_id'])] = $post_parent;
     384                                                $post_parent = 0;
     385                                        }
     386                                }
    420387
    421                 wp_import_cleanup($this->id);
    422                 do_action('import_done', 'wordpress');
     388                                // map the post author
     389                                $author = sanitize_user( $post['post_author'], true );
     390                                if ( isset( $this->processed_authors[$author] ) )
     391                                        $author = $this->processed_authors[$author];
     392                                else
     393                                        $author = (int) get_current_user_id();
    423394
    424                 echo '<h3>'.sprintf(__('All done.', 'wordpress-importer').' <a href="%s">'.__('Have fun!', 'wordpress-importer').'</a>', get_option('home')).'</h3>';
    425         }
     395                                $postdata = array(
     396                                        'import_id' => $post['post_id'], 'post_author' => $author, 'post_date' => $post['post_date'],
     397                                        'post_date_gmt' => $post['post_date_gmt'], 'post_content' => $post['post_content'],
     398                                        'post_excerpt' => $post['post_excerpt'], 'post_title' => $post['post_title'],
     399                                        'post_status' => $post['status'], 'post_name' => $post['post_name'],
     400                                        'comment_status' => $post['comment_status'], 'ping_status' => $post['ping_status'],
     401                                        'guid' => $post['guid'], 'post_parent' => $post_parent, 'menu_order' => $post['menu_order'],
     402                                        'post_type' => $post['post_type'], 'post_password' => $post['post_password']
     403                                );
    426404
    427         function _normalize_tag( $matches ) {
    428                 return '<' . strtolower( $matches[1] );
    429         }
     405                                if ( 'attachment' == $postdata['post_type'] ) {
     406                                        $remote_url = ! empty($post['attachment_url']) ? $post['attachment_url'] : $post['guid'];
     407                                        $comment_post_ID = $post_id = $this->process_attachment( $postdata, $remote_url );
     408                                } else {
     409                                        $comment_post_ID = $post_id = wp_insert_post( $postdata, true );
     410                                }
    430411
    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
     412                                if ( is_wp_error( $post_id ) ) {
     413                                        echo __( 'Error importing post object:', 'wordpress-importer' ) . ' ' . esc_html( $post_id->get_error_message() ) . '<br />';
     414                                        continue;
    498415                                }
    499                                 else {
    500                                         // record the parent for later
    501                                         $this->orphans[intval($post_ID)] = $post_parent;
    502                                 }
    503                         }
    504416
    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 )
     417                                if ( $post['is_sticky'] == 1 )
    524418                                        stick_post( $post_id );
    525 
    526419                        }
    527420
    528                         if ( is_wp_error( $post_id ) )
    529                                 return $post_id;
     421                        // map pre-import ID to local ID
     422                        $this->processed_posts[intval($post['post_id'])] = (int) $post_id;
    530423
    531                         // Memorize old and new ID.
    532                         if ( $post_id && $post_ID ) {
    533                                 $this->post_ids_processed[intval($post_ID)] = intval($post_id);
     424                        // add categories, tags and other terms
     425                        if ( ! empty( $post['terms'] ) ) {
     426                                foreach ( $post['terms'] as $term ) {
     427                                        // back compat with WXR 1.0 map 'tag' to 'post_tag'
     428                                        $taxonomy = ( 'tag' == $term['domain'] ) ? 'post_tag' : $term['domain'];
     429                                        $term_exists = term_exists( $term['slug'], $taxonomy );
     430                                        $term_id = is_array( $term_exists ) ? $term_exists['term_id'] : $term_exists;
     431                                        if ( ! $term_id ) {
     432                                                $t = wp_insert_term( $term['name'], $taxonomy, array( 'slug' => $term['slug'] ) );
     433                                                if ( ! is_wp_error( $t ) ) {
     434                                                        $term_id = $t['term_id'];
     435                                                } else {
     436                                                        echo __( 'Error importing term:', 'wordpress-importer' ) . ' ' . esc_html( $id->get_error_message() ) . '<br />';
     437                                                        continue;
     438                                                }
     439                                        }
     440                                        $terms_to_set[$taxonomy][] = intval( $term_id );
     441                                }
     442
     443                                foreach ( $terms_to_set as $tax => $ids ) {
     444                                        $tt_ids = wp_set_post_terms( $post_id, $ids, $tax );
     445                                }
     446                                unset( $post['terms'], $terms_to_set );
    534447                        }
    535448
    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) )
    551                                                         continue;
     449                        // add/update comments
     450                        if ( ! empty( $post['comments'] ) ) {
     451                                $num_comments = 0;
     452                                $inserted_comments = array();
     453                                foreach ( $post['comments'] as $comment ) {
     454                                        $comment_id     = $comment['comment_id'];
     455                                        $newcomments[$comment_id]['comment_post_ID']      = $comment_post_ID;
     456                                        $newcomments[$comment_id]['comment_author']       = $comment['comment_author'];
     457                                        $newcomments[$comment_id]['comment_author_email'] = $comment['comment_author_email'];
     458                                        $newcomments[$comment_id]['comment_author_IP']    = $comment['comment_author_IP'];
     459                                        $newcomments[$comment_id]['comment_author_url']   = $comment['comment_author_url'];
     460                                        $newcomments[$comment_id]['comment_date']         = $comment['comment_date'];
     461                                        $newcomments[$comment_id]['comment_date_gmt']     = $comment['comment_date_gmt'];
     462                                        $newcomments[$comment_id]['comment_content']      = $comment['comment_content'];
     463                                        $newcomments[$comment_id]['comment_approved']     = $comment['comment_approved'];
     464                                        $newcomments[$comment_id]['comment_type']         = ! empty( $comment['comment_type'] ) ? $comment['comment_type'] : 'comment';
     465                                        $newcomments[$comment_id]['comment_parent']       = $comment['comment_parent'];
     466                                }
     467                                ksort( $newcomments );
     468
     469                                foreach ( $newcomments as $key => $comment ) {
     470                                        // if this is a new post we can skip the comment_exists() check
     471                                        if ( ! $post_exists || ! comment_exists( $comment['comment_author'], $comment['comment_date'] ) ) {
     472                                                if ( isset( $inserted_comments[$comment['comment_parent']] ) )
     473                                                        $comment['comment_parent'] = $inserted_comments[$comment['comment_parent']];
     474                                                $comment = wp_filter_comment( $comment );
     475                                                $inserted_comments[$key] = wp_insert_comment( $comment );
     476                                                $num_comments++;
    552477                                        }
    553                                         $post_cats[] = $cat_ID;
    554478                                }
    555                                 wp_set_post_categories($post_id, $post_cats);
     479                                unset( $newcomments, $inserted_comments, $post['comments'] );
    556480                        }
    557481
    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'];
     482                        // add/update post meta
     483                        if ( isset( $post['postmeta'] ) ) {
     484                                foreach ( $post['postmeta'] as $meta ) {
     485                                        $key = apply_filters( 'import_post_meta_key', $meta['key'] );
     486                                        if ( $key ) {
     487                                                update_post_meta( $post_id, $key, $meta['value'] );
     488                                                do_action( 'import_post_meta', $post_id, $key, $meta['value'] );
    575489                                        }
    576                                         $post_tags[] = intval($tag_id);
    577490                                }
    578                                 wp_set_post_tags($post_id, $post_tags);
    579491                        }
    580492                }
     493        }
    581494
    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');
     495        function process_menu_item( $item ) {
     496                $menu_slug = false;
     497                // loop through terms, assume first nav_menu term is correct menu
     498                foreach ( $item['terms'] as $term ) {
     499                        if ( 'nav_menu' == $term['domain'] ) {
     500                                $menu_slug = $term['slug'];
     501                                break;
    601502                        }
    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++;
    612                                 }
    613                         }
    614503                }
    615504
    616                 if ( $num_comments )
    617                         printf(' '._n('(%s comment)', '(%s comments)', $num_comments, 'wordpress-importer'), $num_comments);
     505                // no nav_menu term associated with this menu item
     506                if ( ! $menu_slug ) {
     507                        _e( 'Menu item skipped due to missing menu slug', 'wordpress-importer' );
     508                        echo '<br />';
     509                        return;
     510                }
    618511
    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' );
     512                $menu_id = term_exists( $menu_slug, 'nav_menu' );
     513                if ( ! $menu_id ) {
     514                        _e( sprintf( 'Menu item skipped due to invalid menu slug: %s', esc_html( $menu_slug ) ), 'wordpress-importer' );
     515                        echo '<br />';
     516                        return;
     517                } else {
     518                        $menu_id = is_array( $menu_id ) ? $menu_id['term_id'] : $menu_id;
     519                }
    625520
    626                         $this->process_post_meta($post_id, $key, $value);
     521                foreach ( $item['postmeta'] as $meta )
     522                        $$meta['key'] = $meta['value'];
    627523
    628                 } }
     524                if ( 'taxonomy' == $_menu_item_type && isset( $this->processed_terms[intval($_menu_item_object_id)] ) ) {
     525                        $_menu_item_object_id = $this->processed_terms[intval($_menu_item_object_id)];
     526                } else if ( 'post_type' == $_menu_item_type && isset( $this->processed_posts[intval($_menu_item_object_id)] ) ) {
     527                        $_menu_item_object_id = $this->processed_posts[intval($_menu_item_object_id)];
     528                } else if ( 'custom' != $_menu_item_type ) {
     529                        // associated object is missing or not imported yet, we'll retry later
     530                        $this->missing_menu_items[] = $item;
     531                        return;
     532                }
    629533
    630                 do_action('import_post_added', $post_id);
    631                 print "</li>\n";
    632         }
    633 
    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);
     534                if ( isset( $this->processed_menu_items[intval($_menu_item_menu_item_parent)] ) ) {
     535                        $_menu_item_menu_item_parent = $this->processed_menu_items[intval($_menu_item_menu_item_parent)];
     536                } else if ( $_menu_item_menu_item_parent ) {
     537                        $this->menu_item_orphans[intval($item['post_id'])] = (int) $_menu_item_menu_item_parent;
     538                        $_menu_item_menu_item_parent = 0;
    640539                }
    641         }
    642540
    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) );
     541                $args = array(
     542                        'menu-item-object-id' => $_menu_item_object_id,
     543                        'menu-item-object' => $_menu_item_object,
     544                        'menu-item-parent-id' => $_menu_item_menu_item_parent,
     545                        'menu-item-position' => intval( $item['menu_order'] ),
     546                        'menu-item-type' => $_menu_item_type,
     547                        'menu-item-title' => $item['post_title'],
     548                        'menu-item-url' => $_menu_item_url,
     549                        'menu-item-description' => $item['post_content'],
     550                        'menu-item-attr-title' => $item['post_excerpt'],
     551                        'menu-item-target' => $_menu_item_target,
     552                        'menu-item-classes' => $_menu_item_classes,
     553                        'menu-item-xfn' => $_menu_item_xfn,
     554                        'menu-item-status' => $item['status']
     555                );
    646556
    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;
     557                $id = wp_update_nav_menu_item( $menu_id, 0, $args );
     558                if ( $id && ! is_wp_error( $id ) )
     559                        $this->processed_menu_items[intval($item['post_id'])] = (int) $id;
     560        }
    650561
    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                         }
     562        function process_attachment( $post, $url ) {
     563                if ( ! ( $this->fetch_attachments && $url ) )
     564                        return new WP_Error( 'attachment_processing_error',
     565                                __( 'Fetching attachments is not allowed or an empty URL was provided', 'wordpress-importer' ) );
    659566
    660                         if ( 0 == filesize( $upload['file'] ) ) {
    661                                 print __( "Zero length file, deleting" , 'wordpress-importer') . "\n";
    662                                 unlink( $upload['file'] );
    663                                 return;
    664                         }
     567                // if the URL is absolute, but does not contain address, then upload it assuming base_site_url
     568                if ( preg_match( '|^/[\w\W]+$|', $url ) )
     569                        $url = rtrim( $this->base_url, '/' ) . $url;
    665570
    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                         }
     571                $upload = $this->fetch_remote_file( $url, $post );
     572                if ( is_wp_error( $upload ) )
     573                        return $upload;
    673574
    674                         $postdata['guid'] = $upload['url'];
     575                if ( $info = wp_check_filetype( $upload['file'] ) )
     576                        $post['post_mime_type'] = $info['type'];
     577                else
     578                        return new WP_Error( 'attachment_processing_error', __('Invalid file type', 'wordpress-importer') );
    675579
    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'] ) );
     580                $post['guid'] = $upload['url'];
    679581
    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                         }
     582                // as per wp-admin/includes/upload.php
     583                $post_id = wp_insert_attachment( $post, $upload['file'] );
     584                wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) );
    687585
    688                         return $post_id;
     586                // remap the thumbnail url.  this isn't perfect because we're just guessing the original url.
     587                if ( preg_match( '@^image/@', $info['type'] ) && $thumb_url = wp_get_attachment_thumb_url( $post_id ) ) {
     588                        $parts = pathinfo( $url );
     589                        $ext = $parts['extension'];
     590                        $name = basename($parts['basename'], ".{$ext}");
     591                        $this->url_remap[$parts['dirname'] . '/' . $name . '.thumbnail.' . $ext] = $thumb_url;
    689592                }
    690                 else {
    691                         printf( __('Skipping attachment <em>%s</em>', 'wordpress-importer'), htmlspecialchars($remote_url) );
    692                 }
     593
     594                return $post_id;
    693595        }
    694596
    695         function fetch_remote_file( $post, $url ) {
     597        function fetch_remote_file( $url, $post ) {
    696598                add_filter( 'http_request_timeout', array( &$this, 'bump_request_timeout' ) );
    697599
    698                 $upload = wp_upload_dir($post['post_date']);
    699 
    700600                // extract the file name and extension from the url
    701                 $file_name = basename($url);
     601                $file_name = basename( $url );
    702602
    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'];
     603                // get placeholder file in the upload dir with a unique, sanitized filename
     604                $upload = wp_upload_bits( $file_name, 0, '', $post['post_date'] );
     605                if ( $upload['error'] )
    707606                        return new WP_Error( 'upload_dir_error', $upload['error'] );
    708                 }
    709607
    710608                // fetch the remote url and write it to the placeholder file
    711                 $headers = wp_get_http($url, $upload['file']);
     609                $headers = wp_get_http( $url, $upload['file'] );
    712610
    713                 //Request failed
     611                // request failed
    714612                if ( ! $headers ) {
    715                         @unlink($upload['file']);
     613                        @unlink( $upload['file'] );
    716614                        return new WP_Error( 'import_file_error', __('Remote server did not respond', 'wordpress-importer') );
    717615                }
    718616
    719617                // make sure the fetch was successful
    720618                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']) ) );
     619                        @unlink( $upload['file'] );
     620                        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']) ) );
    723621                }
    724                 elseif ( isset($headers['content-length']) && filesize($upload['file']) != $headers['content-length'] ) {
    725                         @unlink($upload['file']);
     622
     623                $filesize = filesize( $upload['file'] );
     624
     625                if ( isset( $headers['content-length'] ) && $filesize != $headers['content-length'] ) {
     626                        @unlink( $upload['file'] );
    726627                        return new WP_Error( 'import_file_error', __('Remote file is incorrect size', 'wordpress-importer') );
    727628                }
    728629
    729                 $max_size = $this->max_attachment_size();
    730                 if ( !empty($max_size) and filesize($upload['file']) > $max_size ) {
    731                         @unlink($upload['file']);
     630                if ( 0 == $filesize ) {
     631                        @unlink( $upload['file'] );
     632                        return new WP_Error( 'import_file_error', __('Zero size file downloaded', 'wordpress-importer') );
     633                }
     634
     635                $max_size = (int) $this->max_attachment_size();
     636                if ( ! empty( $max_size ) && $filesize > $max_size ) {
     637                        @unlink( $upload['file'] );
    732638                        return new WP_Error( 'import_file_error', sprintf(__('Remote file is too large, limit is %s', size_format($max_size), 'wordpress-importer')) );
    733639                }
    734640
     
    736642                $this->url_remap[$url] = $upload['url'];
    737643                $this->url_remap[$post['guid']] = $upload['url'];
    738644                // if the remote url is redirected somewhere else, keep track of the destination too
    739                 if ( $headers['x-final-location'] != $url )
     645                if ( isset($headers['x-final-location']) && $headers['x-final-location'] != $url )
    740646                        $this->url_remap[$headers['x-final-location']] = $upload['url'];
    741647
    742648                return $upload;
    743 
    744649        }
    745650
    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         }
     651        function backfill_parents() {
     652                global $wpdb;
    755653
    756         // sort by strlen, longest string first
    757         function cmpr_strlen($a, $b) {
    758                 return strlen($b) - strlen($a);
     654                // find parents for post orphans
     655                foreach ( $this->post_orphans as $child_id => $parent_id ) {
     656                        $local_child_id = $local_parent_id = false;
     657                        if ( isset( $this->processed_posts[$child_id] ) )
     658                                $local_child_id = $this->processed_posts[$child_id];
     659                        if ( isset( $this->processed_posts[$parent_id] ) )
     660                                $local_parent_id = $this->processed_posts[$parent_id];
     661
     662                        if ( $local_child_id && $local_parent_id )
     663                                $wpdb->update( $wpdb->posts, array( 'post_parent' => $local_parent_id ), array( 'ID' => $local_child_id ), '%d', '%d' );
     664                }
     665
     666                // all other posts/terms are imported, retry menu items with missing associated object
     667                $missing_menu_items = $this->missing_menu_items;
     668                foreach ( $missing_menu_items as $item )
     669                        $this->process_menu_item( $item );
     670
     671                // find parents for menu item orphans
     672                foreach ( $this->menu_item_orphans as $child_id => $parent_id ) {
     673                        $local_child_id = $local_parent_id = 0;
     674                        if ( isset( $this->processed_menu_items[$child_id] ) )
     675                                $local_child_id = $this->processed_menu_items[$child_id];
     676                        if ( isset( $this->processed_menu_items[$parent_id] ) )
     677                                $local_parent_id = $this->processed_menu_items[$parent_id];
     678
     679                        if ( $local_child_id && $local_parent_id )
     680                                update_post_meta( $local_child_id, '_menu_item_menu_item_parent', (int) $local_parent_id );
     681                }
    759682        }
    760683
    761         // update url references in post bodies to point to the new local files
    762684        function backfill_attachment_urls() {
     685                global $wpdb;
    763686
    764687                // 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'));
     688                uksort( $this->url_remap, array(&$this, 'cmpr_strlen') );
    766689
    767                 global $wpdb;
    768                 foreach ($this->url_remap as $from_url => $to_url) {
     690                foreach ( $this->url_remap as $from_url => $to_url ) {
    769691                        // 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) );
     692                        $wpdb->query( $wpdb->prepare("UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url) );
    771693                        // 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) );
     694                        $result = $wpdb->query( $wpdb->prepare("UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url) );
    773695                }
    774696        }
    775697
    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;
     698        function parse( $file ) {
     699                $parser = new WXR_Parser();
     700                return $parser->parse( $file );
     701        }
    779702
    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];
     703        function header() {
     704                echo '<div class="wrap">';
     705                screen_icon();
     706                echo '<h2>' . __( 'Import WordPress', 'wordpress-importer' ) . '</h2>';
     707        }
    786708
    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                 }
     709        function footer() {
     710                echo '</div>';
    791711        }
    792712
    793         function is_valid_meta_key($key) {
     713        function greet() {
     714                echo '<div class="narrow">';
     715                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>';
     716                echo '<p>'.__( 'Choose a WXR file to upload, then click Upload file and import.', 'wordpress-importer' ).'</p>';
     717                wp_import_upload_form( 'admin.php?import=wordpress&amp;step=1' );
     718                echo '</div>';
     719        }
     720
     721        function is_valid_meta_key( $key ) {
    794722                // skip attachment metadata since we'll regenerate it from scratch
    795                 if ( $key == '_wp_attached_file' || $key == '_wp_attachment_metadata' )
     723                // skip _edit_lock and _edit_last as not useful
     724                if ( in_array( $key, array( '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock', '_edit_last' ) ) )
    796725                        return false;
    797726                return $key;
    798727        }
    799728
    800729        // give the user the option of creating new users to represent authors in the import file?
    801730        function allow_create_users() {
    802                 return apply_filters('import_allow_create_users', true);
     731                return apply_filters( 'import_allow_create_users', true );
    803732        }
    804733
    805734        // give the user the option of downloading and importing attached files
    806735        function allow_fetch_attachments() {
    807                 return apply_filters('import_allow_fetch_attachments', true);
     736                return apply_filters( 'import_allow_fetch_attachments', true );
    808737        }
    809738
    810         function max_attachment_size() {
    811                 // can be overridden with a filter - 0 means no limit
    812                 return apply_filters('import_attachment_size_limit', 0);
     739        function bump_request_timeout() {
     740                return 60;
    813741        }
    814742
    815         function import_start() {
    816                 wp_defer_term_counting(true);
    817                 wp_defer_comment_counting(true);
    818                 do_action('import_start');
     743        function max_attachment_size() {
     744                return apply_filters( 'import_attachment_size_limit', 0 );
    819745        }
    820746
    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);
     747        function cmpr_strlen( $a, $b ) {
     748                return strlen($b) - strlen($a);
    830749        }
    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         }
    903750}
    904751
    905752/**