ug ); $requires_remapping = true; // Wipe the parent id for now $data['parent'] = 0; } } foreach ( $data as $key => $value ) { if ( ! isset( $allowed[ $key ] ) ) { continue; } $termdata[ $key ] = $data[ $key ]; } $result = wp_insert_term( $data['name'], $data['taxonomy'], $termdata ); if ( is_wp_error( $result ) ) { $this->logger->warning( sprintf( __( 'Failed to import %s %s', 'wordpress-importer' ), $data['taxonomy'], $data['name'] ) ); $this->logger->debug( $result->get_error_message() ); do_action( 'wp_import_insert_term_failed', $result, $data ); /** * Term processing failed. * * @param WP_Error $result Error object. * @param array $data Raw data imported for the term. * @param array $meta Meta data supplied for the term. */ do_action( 'wxr_importer.process_failed.term', $result, $data, $meta ); return false; } $term_id = $result['term_id']; // Now prepare to map this new term. $this->mapping['term'][ $mapping_key ] = $term_id; $this->mapping['term_id'][ $original_id ] = $term_id; $this->mapping['term_slug'][ $term_slug ] = $term_id; /* * Fix for OCDI! * The parent will be updated later in post_process_terms * we will need both the term_id AND the term_taxonomy to retrieve existing * term attributes. Those attributes will be returned with the corrected parent, * using wp_update_term. * Pass both the term_id along with the term_taxonomy as key=>value * in the requires_remapping['term'] array. */ if ( $requires_remapping ) { $this->requires_remapping['term'][ $term_id ] = $data['taxonomy']; } $this->logger->info( sprintf( __( 'Imported "%s" (%s)', 'wordpress-importer' ), $data['name'], $data['taxonomy'] ) ); $this->logger->debug( sprintf( __( 'Term %d remapped to %d', 'wordpress-importer' ), $original_id, $term_id ) ); // Actuall process of the term meta data. $this->process_term_meta( $meta, $term_id, $data ); do_action( 'wp_import_insert_term', $term_id, $data ); /** * Term processing completed. * * @param int $term_id New term ID. * @param array $data Raw data imported for the term. */ do_action( 'wxr_importer.processed.term', $term_id, $data ); } /** * Process and import term meta items. * * @param array $meta List of meta data arrays * @param int $term_id Term ID to associate with * @param array $term Term data * @return int|bool Number of meta items imported on success, false otherwise. */ protected function process_term_meta( $meta, $term_id, $term ) { if ( empty( $meta ) ) { return true; } foreach ( $meta as $meta_item ) { /** * Pre-process term meta data. * * @param array $meta_item Meta data. (Return empty to skip.) * @param int $term_id Term the meta is attached to. */ $meta_item = apply_filters( 'wxr_importer.pre_process.term_meta', $meta_item, $term_id ); if ( empty( $meta_item ) ) { continue; } $key = apply_filters( 'import_term_meta_key', $meta_item['key'], $term_id, $term ); $value = false; if ( $key ) { // Export gets meta straight from the DB so could have a serialized string. if ( ! $value ) { $value = maybe_unserialize( $meta_item['value'] ); } $result = add_term_meta( $term_id, $key, $value ); if ( is_wp_error( $result ) ) { $this->logger->warning( sprintf( __( 'Failed to add metakey: %s, metavalue: %s to term_id: %d', 'wordpress-importer' ), $key, $value, $term_id ) ); do_action( 'wxr_importer.process_failed.termmeta', $result, $meta_item, $term_id, $term ); } else { $this->logger->debug( sprintf( __( 'Meta for term_id %d : %s => %s ; successfully added!', 'wordpress-importer' ), $term_id, $key, $value ) ); } do_action( 'import_term_meta', $term_id, $key, $value ); } } return true; } /** * 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 */ protected function fetch_remote_file( $url, $post ) { // extract the file name and extension from the url $file_name = basename( $url ); // get placeholder file in the upload dir with a unique, sanitized filename $upload = wp_upload_bits( $file_name, 0, '', $post['upload_date'] ); if ( $upload['error'] ) { return new \WP_Error( 'upload_dir_error', $upload['error'] ); } // fetch the remote url and write it to the placeholder file $response = wp_remote_get( $url, array( 'stream' => true, 'filename' => $upload['file'], ) ); // request failed if ( is_wp_error( $response ) ) { unlink( $upload['file'] ); return $response; } $code = (int) wp_remote_retrieve_response_code( $response ); // make sure the fetch was successful if ( $code !== 200 ) { unlink( $upload['file'] ); return new \WP_Error( 'import_file_error', sprintf( __( 'Remote server returned %1$d %2$s for %3$s', 'wordpress-importer' ), $code, get_status_header_desc( $code ), $url ) ); } $filesize = filesize( $upload['file'] ); $headers = wp_remote_retrieve_headers( $response ); // OCDI fix! // Smaller images with server compression do not pass this rule. // More info here: https://github.com/proteusthemes/WordPress-Importer/pull/2 // // if ( isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) { // unlink( $upload['file'] ); // return new \WP_Error( 'import_file_error', __( 'Remote file is incorrect size', 'wordpress-importer' ) ); // } if ( 0 === $filesize ) { unlink( $upload['file'] ); return new \WP_Error( 'import_file_error', __( 'Zero size file downloaded', 'wordpress-importer' ) ); } $max_size = (int) $this->max_attachment_size(); if ( ! empty( $max_size ) && $filesize > $max_size ) { unlink( $upload['file'] ); $message = sprintf( __( 'Remote file is too large, limit is %s', 'wordpress-importer' ), size_format( $max_size ) ); return new \WP_Error( 'import_file_error', $message ); } return $upload; } protected function post_process() { // Time to tackle any left-over bits if ( ! empty( $this->requires_remapping['post'] ) ) { $this->post_process_posts( $this->requires_remapping['post'] ); } if ( ! empty( $this->requires_remapping['comment'] ) ) { $this->post_process_comments( $this->requires_remapping['comment'] ); } if ( ! empty( $this->requires_remapping['term'] ) ) { $this->post_process_terms( $this->requires_remapping['term'] ); } } protected function post_process_posts( $todo ) { foreach ( $todo as $post_id => $_ ) { $this->logger->debug( sprintf( // Note: title intentionally not used to skip extra processing // for when debug logging is off __( 'Running post-processing for post %d', 'wordpress-importer' ), $post_id ) ); $data = array(); $parent_id = get_post_meta( $post_id, '_wxr_import_parent', true ); if ( ! empty( $parent_id ) ) { // Have we imported the parent now? if ( isset( $this->mapping['post'][ $parent_id ] ) ) { $data['post_parent'] = $this->mapping['post'][ $parent_id ]; } else { $this->logger->warning( sprintf( __( 'Could not find the post parent for "%s" (post #%d)', 'wordpress-importer' ), get_the_title( $post_id ), $post_id ) ); $this->logger->debug( sprintf( __( 'Post %d was imported with parent %d, but could not be found', 'wordpress-importer' ), $post_id, $parent_id ) ); } } $author_slug = get_post_meta( $post_id, '_wxr_import_user_slug', true ); if ( ! empty( $author_slug ) ) { // Have we imported the user now? if ( isset( $this->mapping['user_slug'][ $author_slug ] ) ) { $data['post_author'] = $this->mapping['user_slug'][ $author_slug ]; } else { $this->logger->warning( sprintf( __( 'Could not find the author for "%s" (post #%d)', 'wordpress-importer' ), get_the_title( $post_id ), $post_id ) ); $this->logger->debug( sprintf( __( 'Post %d was imported with author "%s", but could not be found', 'wordpress-importer' ), $post_id, $author_slug ) ); } } $has_attachments = get_post_meta( $post_id, '_wxr_import_has_attachment_refs', true ); if ( ! empty( $has_attachments ) ) { $post = get_post( $post_id ); $content = $post->post_content; // Replace all the URLs we've got $new_content = str_replace( array_keys( $this->url_remap ), $this->url_remap, $content ); if ( $new_content !== $content ) { $data['post_content'] = $new_content; } } if ( get_post_type( $post_id ) === 'nav_menu_item' ) { $this->post_process_menu_item( $post_id ); } // Do we have updates to make? if ( empty( $data ) ) { $this->logger->debug( sprintf( __( 'Post %d was marked for post-processing, but none was required.', 'wordpress-importer' ), $post_id ) ); continue; } // Run the update $data['ID'] = $post_id; $result = wp_update_post( $data, true ); if ( is_wp_error( $result ) ) { $this->logger->warning( sprintf( __( 'Could not update "%s" (post #%d) with mapped data', 'wordpress-importer' ), get_the_title( $post_id ), $post_id ) ); $this->logger->debug( $result->get_error_message() ); continue; } // Clear out our temporary meta keys delete_post_meta( $post_id, '_wxr_import_parent' ); delete_post_meta( $post_id, '_wxr_import_user_slug' ); delete_post_meta( $post_id, '_wxr_import_has_attachment_refs' ); } } protected function post_process_menu_item( $post_id ) { $menu_object_id = get_post_meta( $post_id, '_wxr_import_menu_item', true ); if ( empty( $menu_object_id ) ) { // No processing needed! return; } $menu_item_type = get_post_meta( $post_id, '_menu_item_type', true ); switch ( $menu_item_type ) { case 'taxonomy': if ( isset( $this->mapping['term_id'][ $menu_object_id ] ) ) { $menu_object = $this->mapping['term_id'][ $menu_object_id ]; } break; case 'post_type': if ( isset( $this->mapping['post'][ $menu_object_id ] ) ) { $menu_object = $this->mapping['post'][ $menu_object_id ]; } break; default: // Cannot handle this. return; } if ( ! empty( $menu_object ) ) { update_post_meta( $post_id, '_menu_item_object_id', wp_slash( $menu_object ) ); } else { $this->logger->warning( sprintf( __( 'Could not find the menu object for "%s" (post #%d)', 'wordpress-importer' ), get_the_title( $post_id ), $post_id ) ); $this->logger->debug( sprintf( __( 'Post %d was imported with object "%d" of type "%s", but could not be found', 'wordpress-importer' ), $post_id, $menu_object_id, $menu_item_type ) ); } delete_post_meta( $post_id, '_wxr_import_menu_item' ); } protected function post_process_comments( $todo ) { foreach ( $todo as $comment_id => $_ ) { $data = array(); $parent_id = get_comment_meta( $comment_id, '_wxr_import_parent', true ); if ( ! empty( $parent_id ) ) { // Have we imported the parent now? if ( isset( $this->mapping['comment'][ $parent_id ] ) ) { $data['comment_parent'] = $this->mapping['comment'][ $parent_id ]; } else { $this->logger->warning( sprintf( __( 'Could not find the comment parent for comment #%d', 'wordpress-importer' ), $comment_id ) ); $this->logger->debug( sprintf( __( 'Comment %d was imported with parent %d, but could not be found', 'wordpress-importer' ), $comment_id, $parent_id ) ); } } $author_id = get_comment_meta( $comment_id, '_wxr_import_user', true ); if ( ! empty( $author_id ) ) { // Have we imported the user now? if ( isset( $this->mapping['user'][ $author_id ] ) ) { $data['user_id'] = $this->mapping['user'][ $author_id ]; } else { $this->logger->warning( sprintf( __( 'Could not find the author for comment #%d', 'wordpress-importer' ), $comment_id ) ); $this->logger->debug( sprintf( __( 'Comment %d was imported with author %d, but could not be found', 'wordpress-importer' ), $comment_id, $author_id ) ); } } // Do we have updates to make? if ( empty( $data ) ) { continue; } // Run the update $data['comment_ID'] = $comment_ID; $result = wp_update_comment( wp_slash( $data ) ); if ( empty( $result ) ) { $this->logger->warning( sprintf( __( 'Could not update comment #%d with mapped data', 'wordpress-importer' ), $comment_id ) ); continue; } // Clear out our temporary meta keys delete_comment_meta( $comment_id, '_wxr_import_parent' ); delete_comment_meta( $comment_id, '_wxr_import_user' ); } } /** * There is no explicit 'top' or 'root' for a hierarchy of WordPress terms * Terms without a parent, or parent=0 are either unconnected (orphans) * or top-level siblings without an explicit root parent * An unconnected term (orphan) should have a null parent_slug * Top-level siblings without an explicit root parent, shall be identified * with the parent_slug: top * [we'll map parent_slug: top into parent 0] */ protected function post_process_terms( $terms_to_be_remapped ) { $this->mapping['term_slug']['top'] = 0; // The term_id and term_taxonomy are passed-in with $this->requires_remapping['term']. foreach ( $terms_to_be_remapped as $termid => $term_taxonomy ) { // Basic check. if( empty( $termid ) || ! is_numeric( $termid ) ) { $this->logger->warning( sprintf( __( 'Faulty term_id provided in terms-to-be-remapped array %s', 'wordpress-importer' ), $termid ) ); continue; } // This cast to integer may be unnecessary. $term_id = (int) $termid; if( empty( $term_taxonomy ) ){ $this->logger->warning( sprintf( __( 'No taxonomy provided in terms-to-be-remapped array for term #%d', 'wordpress-importer' ), $term_id ) ); continue; } $data = array(); $parent_slug = get_term_meta( $term_id, '_wxr_import_parent', true ); if ( empty( $parent_slug ) ) { $this->logger->warning( sprintf( __( 'No parent_slug identified in remapping-array for term: %d', 'wordpress-importer' ), $term_id ) ); continue; } if ( ! isset( $this->mapping['term_slug'][ $parent_slug ] ) || ! is_numeric( $this->mapping['term_slug'][ $parent_slug ] ) ) { $this->logger->warning( sprintf( __( 'The term(%d)"s parent_slug (%s) is not found in the remapping-array.', 'wordpress-importer' ), $term_id, $parent_slug ) ); continue; } $mapped_parent = (int) $this->mapping['term_slug'][ $parent_slug ]; $termattributes = get_term_by( 'id', $term_id, $term_taxonomy, ARRAY_A ); // Note: the default OBJECT return results in a reserved-word clash with 'parent' [$termattributes->parent], so instead return an associative array. if ( empty( $termattributes ) ) { $this->logger->warning( sprintf( __( 'No data returned by get_term_by for term_id #%d', 'wordpress-importer' ), $term_id ) ); continue; } // Check if the correct parent id is already correctly mapped. if ( isset( $termattributes['parent'] ) && $termattributes['parent'] == $mapped_parent ) { // Clear out our temporary meta key. delete_term_meta( $term_id, '_wxr_import_parent' ); continue; } // Otherwise set the mapped parent and update the term. $termattributes['parent'] = $mapped_parent; $result = wp_update_term( $term_id, $termattributes['taxonomy'], $termattributes ); if ( is_wp_error( $result ) ) { $this->logger->warning( sprintf( __( 'Could not update "%s" (term #%d) with mapped data', 'wordpress-importer' ), $termattributes['name'], $term_id ) ); $this->logger->debug( $result->get_error_message() ); continue; } // Clear out our temporary meta key. delete_term_meta( $term_id, '_wxr_import_parent' ); $this->logger->debug( sprintf( __( 'Term %d was successfully updated with parent %d', 'wordpress-importer' ), $term_id, $mapped_parent ) ); } } /** * Use stored mapping information to update old attachment URLs */ protected function replace_attachment_urls_in_content() { global $wpdb; // 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 $query = $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url ); $wpdb->query( $query ); // remap enclosure urls $query = $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url ); $result = $wpdb->query( $query ); } } /** * Update _thumbnail_id meta to new, imported attachment IDs */ function remap_featured_images() { if ( empty( $this->featured_images ) ) { return; } $this->logger->info( esc_html__( 'Starting remapping of featured images', 'wordpress-importer' ) ); // Cycle through posts that have a featured image. foreach ( $this->featured_images as $post_id => $value ) { if ( isset( $this->mapping['post'][ $value ] ) ) { $new_id = $this->mapping['post'][ $value ]; // Only update if there's a difference. if ( $new_id !== $value ) { $this->logger->info( sprintf( esc_html__( 'Remapping featured image ID %d to new ID %d for post ID %d', 'wordpress-importer' ), $value, $new_id, $post_id ) ); update_post_meta( $post_id, '_thumbnail_id', $new_id ); } } } } /** * 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 */ public 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' ) ) ) { return false; } return $key; } /** * 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 */ protected 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 * * @access protected * @return int 60 */ function bump_request_timeout($val) { return 60; } // return the difference in length between two strings function cmpr_strlen( $a, $b ) { return strlen( $b ) - strlen( $a ); } /** * Prefill existing post data. * * This preloads all GUIDs into memory, allowing us to avoid hitting the * database when we need to check for existence. With larger imports, this * becomes prohibitively slow to perform SELECT queries on each. * * By preloading all this data into memory, it's a constant-time lookup in * PHP instead. However, this does use a lot more memory, so for sites doing * small imports onto a large site, it may be a better tradeoff to use * on-the-fly checking instead. */ protected function prefill_existing_posts() { global $wpdb; $posts = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts}" ); foreach ( $posts as $item ) { $this->exists['post'][ $item->guid ] = $item->ID; } } /** * Does the post exist? * * @param array $data Post data to check against. * @return int|bool Existing post ID if it exists, false otherwise. */ protected function post_exists( $data ) { // Constant-time lookup if we prefilled $exists_key = $data['guid']; if ( $this->options['prefill_existing_posts'] ) { // OCDI: fix for custom post types. The guids in the prefilled section are escaped, so these ones should be as well. $exists_key = htmlentities( $exists_key ); return isset( $this->exists['post'][ $exists_key ] ) ? $this->exists['post'][ $exists_key ] : false; } // No prefilling, but might have already handled it if ( isset( $this->exists['post'][ $exists_key ] ) ) { return $this->exists['post'][ $exists_key ]; } // Still nothing, try post_exists, and cache it $exists = post_exists( $data['post_title'], $data['post_content'], $data['post_date'] ); $this->exists['post'][ $exists_key ] = $exists; return $exists; } /** * Mark the post as existing. * * @param array $data Post data to mark as existing. * @param int $post_id Post ID. */ protected function mark_post_exists( $data, $post_id ) { $exists_key = $data['guid']; $this->exists['post'][ $exists_key ] = $post_id; } /** * Prefill existing comment data. * * @see self::prefill_existing_posts() for justification of why this exists. */ protected function prefill_existing_comments() { global $wpdb; $posts = $wpdb->get_results( "SELECT comment_ID, comment_author, comment_date FROM {$wpdb->comments}" ); foreach ( $posts as $item ) { $exists_key = sha1( $item->comment_author . ':' . $item->comment_date ); $this->exists['comment'][ $exists_key ] = $item->comment_ID; } } /** * Does the comment exist? * * @param array $data Comment data to check against. * @return int|bool Existing comment ID if it exists, false otherwise. */ protected function comment_exists( $data ) { $exists_key = sha1( $data['comment_author'] . ':' . $data['comment_date'] ); // Constant-time lookup if we prefilled if ( $this->options['prefill_existing_comments'] ) { return isset( $this->exists['comment'][ $exists_key ] ) ? $this->exists['comment'][ $exists_key ] : false; } // No prefilling, but might have already handled it if ( isset( $this->exists['comment'][ $exists_key ] ) ) { return $this->exists['comment'][ $exists_key ]; } // Still nothing, try comment_exists, and cache it $exists = comment_exists( $data['comment_author'], $data['comment_date'] ); $this->exists['comment'][ $exists_key ] = $exists; return $exists; } /** * Mark the comment as existing. * * @param array $data Comment data to mark as existing. * @param int $comment_id Comment ID. */ protected function mark_comment_exists( $data, $comment_id ) { $exists_key = sha1( $data['comment_author'] . ':' . $data['comment_date'] ); $this->exists['comment'][ $exists_key ] = $comment_id; } /** * Prefill existing term data. * * @see self::prefill_existing_posts() for justification of why this exists. */ protected function prefill_existing_terms() { global $wpdb; $query = "SELECT t.term_id, tt.taxonomy, t.slug FROM {$wpdb->terms} AS t"; $query .= " JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id"; $terms = $wpdb->get_results( $query ); foreach ( $terms as $item ) { $exists_key = sha1( $item->taxonomy . ':' . $item->slug ); $this->exists['term'][ $exists_key ] = $item->term_id; } } /** * Does the term exist? * * @param array $data Term data to check against. * @return int|bool Existing term ID if it exists, false otherwise. */ protected function term_exists( $data ) { $exists_key = sha1( $data['taxonomy'] . ':' . $data['slug'] ); // Constant-time lookup if we prefilled if ( $this->options['prefill_existing_terms'] ) { return isset( $this->exists['term'][ $exists_key ] ) ? $this->exists['term'][ $exists_key ] : false; } // No prefilling, but might have already handled it if ( isset( $this->exists['term'][ $exists_key ] ) ) { return $this->exists['term'][ $exists_key ]; } // Still nothing, try comment_exists, and cache it $exists = term_exists( $data['slug'], $data['taxonomy'] ); if ( is_array( $exists ) ) { $exists = $exists['term_id']; } $this->exists['term'][ $exists_key ] = $exists; return $exists; } /** * Mark the term as existing. * * @param array $data Term data to mark as existing. * @param int $term_id Term ID. */ protected function mark_term_exists( $data, $term_id ) { $exists_key = sha1( $data['taxonomy'] . ':' . $data['slug'] ); $this->exists['term'][ $exists_key ] = $term_id; } }