: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* If fetching attachments is enabled then attempt to create a new attachment
* @param array $post Attachment post details from WXR
* @param string $url URL to fetch attachment from
* @return int|WP_Error Post ID on success, WP_Error otherwise
function process_attachment( $post, $url ) {
if ( ! $this->fetch_attachments ) {
'attachment_processing_error',
__( 'Fetching attachments is not enabled', 'wordpress-importer' )
// if the URL is absolute, but does not contain address, then upload it assuming base_site_url
if ( preg_match( '|^/[\w\W]+$|', $url ) ) {
$url = rtrim( $this->base_url, '/' ) . $url;
$upload = $this->fetch_remote_file( $url, $post );
if ( is_wp_error( $upload ) ) {
$info = wp_check_filetype( $upload['file'] );
$post['post_mime_type'] = $info['type'];
return new WP_Error( 'attachment_processing_error', __( 'Invalid file type', 'wordpress-importer' ) );
$post['guid'] = $upload['url'];
// as per wp-admin/includes/upload.php
$post_id = wp_insert_attachment( $post, $upload['file'] );
wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) );
// remap resized image URLs, works by stripping the extension and remapping the URL stub.
if ( preg_match( '!^image/!', $info['type'] ) ) {
$parts = pathinfo( $url );
$name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2
$parts_new = pathinfo( $upload['url'] );
$name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" );
$this->url_remap[ $parts['dirname'] . '/' . $name ] = $parts_new['dirname'] . '/' . $name_new;
* Attempt to download a remote file attachment
* @param string $url URL of item to fetch
* @param array $post Attachment details
* @return array|WP_Error Local file location details on success, WP_Error otherwise
function fetch_remote_file( $url, $post ) {
// Extract the file name from the URL.
$path = parse_url( $url, PHP_URL_PATH );
if ( is_string( $path ) ) {
$file_name = basename( $path );
$file_name = md5( $url );
$tmp_file_name = wp_tempnam( $file_name );
if ( ! $tmp_file_name ) {
return new WP_Error( 'import_no_file', __( 'Could not create temporary file.', 'wordpress-importer' ) );
// Fetch the remote URL and write it to the placeholder file.
$remote_response = wp_safe_remote_get(
'filename' => $tmp_file_name,
'Accept-Encoding' => 'identity',
if ( is_wp_error( $remote_response ) ) {
@unlink( $tmp_file_name );
/* translators: 1: The WordPress error message. 2: The WordPress error code. */
__( 'Request failed due to an error: %1$s (%2$s)', 'wordpress-importer' ),
esc_html( $remote_response->get_error_message() ),
esc_html( $remote_response->get_error_code() )
$remote_response_code = (int) wp_remote_retrieve_response_code( $remote_response );
// Make sure the fetch was successful.
if ( 200 !== $remote_response_code ) {
@unlink( $tmp_file_name );
/* translators: 1: The HTTP error message. 2: The HTTP error code. */
__( 'Remote server returned the following unexpected result: %1$s (%2$s)', 'wordpress-importer' ),
get_status_header_desc( $remote_response_code ),
esc_html( $remote_response_code )
$headers = wp_remote_retrieve_headers( $remote_response );
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', __( 'Remote server did not respond', 'wordpress-importer' ) );
$filesize = (int) filesize( $tmp_file_name );
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', __( 'Zero size file downloaded', 'wordpress-importer' ) );
if ( ! isset( $headers['content-encoding'] ) && isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) {
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', __( 'Downloaded file has incorrect size', 'wordpress-importer' ) );
$max_size = (int) $this->max_attachment_size();
if ( ! empty( $max_size ) && $filesize > $max_size ) {
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', sprintf( __( 'Remote file is too large, limit is %s', 'wordpress-importer' ), size_format( $max_size ) ) );
// Override file name with Content-Disposition header value.
if ( ! empty( $headers['content-disposition'] ) ) {
$file_name_from_disposition = self::get_filename_from_disposition( (array) $headers['content-disposition'] );
if ( $file_name_from_disposition ) {
$file_name = $file_name_from_disposition;
// Set file extension if missing.
$file_ext = pathinfo( $file_name, PATHINFO_EXTENSION );
if ( ! $file_ext && ! empty( $headers['content-type'] ) ) {
$extension = self::get_file_extension_by_mime_type( $headers['content-type'] );
$file_name = "{$file_name}.{$extension}";
// Handle the upload like _wp_handle_upload() does.
$wp_filetype = wp_check_filetype_and_ext( $tmp_file_name, $file_name );
$ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
$type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
$proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect.
if ( $proper_filename ) {
$file_name = $proper_filename;
if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
return new WP_Error( 'import_file_error', __( 'Sorry, this file type is not permitted for security reasons.', 'wordpress-importer' ) );
$uploads = wp_upload_dir( $post['upload_date'] );
if ( ! ( $uploads && false === $uploads['error'] ) ) {
return new WP_Error( 'upload_dir_error', $uploads['error'] );
// Move the file to the uploads dir.
$file_name = wp_unique_filename( $uploads['path'], $file_name );
$new_file = $uploads['path'] . "/$file_name";
$move_new_file = copy( $tmp_file_name, $new_file );
if ( ! $move_new_file ) {
@unlink( $tmp_file_name );
return new WP_Error( 'import_file_error', __( 'The uploaded file could not be moved', 'wordpress-importer' ) );
// Set correct file permissions.
$stat = stat( dirname( $new_file ) );
$perms = $stat['mode'] & 0000666;
chmod( $new_file, $perms );
'url' => $uploads['url'] . "/$file_name",
'type' => $wp_filetype['type'],
// keep track of the old and new urls so we can substitute them later
$this->url_remap[ $url ] = $upload['url'];
$this->url_remap[ $post['guid'] ] = $upload['url']; // r13735, really needed?
// keep track of the destination if the remote url is redirected somewhere else
if ( isset( $headers['x-final-location'] ) && $headers['x-final-location'] != $url ) {
$this->url_remap[ $headers['x-final-location'] ] = $upload['url'];
* Attempt to associate posts and menu items with previously missing parents
* An imported post's parent may not have been imported when it was first created
* so try again. Similarly for child menu items and menu items which were missing
* the object (e.g. post) they represent in the menu
function backfill_parents() {
// find parents for post orphans
foreach ( $this->post_orphans as $child_id => $parent_id ) {
$local_parent_id = false;
if ( isset( $this->processed_posts[ $child_id ] ) ) {
$local_child_id = $this->processed_posts[ $child_id ];
if ( isset( $this->processed_posts[ $parent_id ] ) ) {
$local_parent_id = $this->processed_posts[ $parent_id ];
if ( $local_child_id && $local_parent_id ) {
$wpdb->update( $wpdb->posts, array( 'post_parent' => $local_parent_id ), array( 'ID' => $local_child_id ), '%d', '%d' );
clean_post_cache( $local_child_id );
// all other posts/terms are imported, retry menu items with missing associated object
$missing_menu_items = $this->missing_menu_items;
foreach ( $missing_menu_items as $item ) {
$this->process_menu_item( $item );
// find parents for menu item orphans
foreach ( $this->menu_item_orphans as $child_id => $parent_id ) {
if ( isset( $this->processed_menu_items[ $child_id ] ) ) {
$local_child_id = $this->processed_menu_items[ $child_id ];
if ( isset( $this->processed_menu_items[ $parent_id ] ) ) {
$local_parent_id = $this->processed_menu_items[ $parent_id ];
if ( $local_child_id && $local_parent_id ) {
update_post_meta( $local_child_id, '_menu_item_menu_item_parent', (int) $local_parent_id );
* Use stored mapping information to update old attachment URLs
function backfill_attachment_urls() {
// make sure we do the longest urls first, in case one is a substring of another
uksort( $this->url_remap, array( &$this, 'cmpr_strlen' ) );
foreach ( $this->url_remap as $from_url => $to_url ) {
// remap urls in post_content
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url ) );
$result = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url ) );
* Update _thumbnail_id meta to new, imported attachment IDs
function remap_featured_images() {
// cycle through posts that have a featured image
foreach ( $this->featured_images as $post_id => $value ) {
if ( isset( $this->processed_posts[ $value ] ) ) {
$new_id = $this->processed_posts[ $value ];
// only update if there's a difference
if ( $new_id != $value ) {
update_post_meta( $post_id, '_thumbnail_id', $new_id );
* @param string $file Path to WXR file for parsing
* @return array Information gathered from the WXR file
function parse( $file ) {
$parser = new WXR_Parser();
return $parser->parse( $file );
// Display import page title
echo '<div class="wrap">';
echo '<h2>' . __( 'Import WordPress', 'wordpress-importer' ) . '</h2>';
$updates = get_plugin_updates();
$basename = plugin_basename( __FILE__ );
if ( isset( $updates[ $basename ] ) ) {
$update = $updates[ $basename ];
echo '<div class="error"><p><strong>';
printf( __( 'A new version of this importer is available. Please update to version %s to ensure compatibility with newer export files.', 'wordpress-importer' ), $update->update->new_version );
echo '</strong></p></div>';
* Display introductory text and file upload form
echo '<div class="narrow">';
echo '<p>' . __( 'Howdy! Upload your WordPress eXtended RSS (WXR) file and we’ll import the posts, pages, comments, custom fields, categories, and tags into this site.', 'wordpress-importer' ) . '</p>';
echo '<p>' . __( 'Choose a WXR (.xml) file to upload, then click Upload file and import.', 'wordpress-importer' ) . '</p>';
wp_import_upload_form( 'admin.php?import=wordpress&step=1' );
* Decide if the given meta key maps to information we will want to import
* @param string $key The meta key to check
* @return string|bool The key if we do want to import, false if not
function is_valid_meta_key( $key ) {
// skip attachment metadata since we'll regenerate it from scratch
// skip _edit_lock as not relevant for import
if ( in_array( $key, array( '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ), true ) ) {
* Decide whether or not the importer is allowed to create users.
* Default is true, can be filtered via import_allow_create_users
* @return bool True if creating users is allowed
function allow_create_users() {
return apply_filters( 'import_allow_create_users', true );
* Decide whether or not the importer should attempt to download attachment files.
* Default is true, can be filtered via import_allow_fetch_attachments. The choice
* made at the import options screen must also be true, false here hides that checkbox.
* @return bool True if downloading attachments is allowed
function allow_fetch_attachments() {
return apply_filters( 'import_allow_fetch_attachments', true );
* Decide what the maximum file size for downloaded attachments is.
* Default is 0 (unlimited), can be filtered via import_attachment_size_limit
* @return int Maximum attachment file size to import
function max_attachment_size() {
return apply_filters( 'import_attachment_size_limit', 0 );
* Added to http_request_timeout filter to force timeout at 60 seconds during import
function bump_request_timeout( $val ) {
// return the difference in length between two strings
function cmpr_strlen( $a, $b ) {
return strlen( $b ) - strlen( $a );
* Parses filename from a Content-Disposition header value.
* content-disposition = "Content-Disposition" ":"
* disposition-type *( ";" disposition-parm )
* disposition-type = "inline" | "attachment" | disp-ext-type
* disposition-parm = filename-parm | disp-ext-parm
* filename-parm = "filename" "=" value
* | "filename*" "=" ext-value
* disp-ext-parm = token "=" value
* | ext-token "=" ext-value
* ext-token = <the characters in token, followed by "*">
* @see WP_REST_Attachments_Controller::get_filename_from_disposition()
* @link http://tools.ietf.org/html/rfc2388
* @link http://tools.ietf.org/html/rfc6266
* @param string[] $disposition_header List of Content-Disposition header values.
* @return string|null Filename if available, or null if not found.
protected static function get_filename_from_disposition( $disposition_header ) {
foreach ( $disposition_header as $value ) {
if ( strpos( $value, ';' ) === false ) {
list( $type, $attr_parts ) = explode( ';', $value, 2 );
$attr_parts = explode( ';', $attr_parts );
foreach ( $attr_parts as $part ) {
if ( strpos( $part, '=' ) === false ) {
list( $key, $value ) = explode( '=', $part, 2 );
$attributes[ trim( $key ) ] = trim( $value );
if ( empty( $attributes['filename'] ) ) {
$filename = trim( $attributes['filename'] );
// Unquote quoted filename, but after trimming.
if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, -1, 1 ) === '"' ) {
$filename = substr( $filename, 1, -1 );
* Retrieves file extension by mime type.
* @param string $mime_type Mime type to search extension for.
* @return string|null File extension if available, or null if not found.
protected static function get_file_extension_by_mime_type( $mime_type ) {
if ( is_array( $map ) ) {
return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null;
$mime_types = wp_get_mime_types();
$map = array_flip( $mime_types );
// Some types have multiple extensions, use only the first one.
foreach ( $map as $type => $extensions ) {
$map[ $type ] = strtok( $extensions, '|' );
return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null;