PluginProbe ʕ •ᴥ•ʔ
Starter Sites & Templates by Neve / 1.4.1
Starter Sites & Templates by Neve v1.4.1
1.4.1 1.4.0 1.3.0 1.2.29 1.2.28 1.2.6 1.2.7 1.2.8 1.2.9 trunk 1.0.10 1.0.11 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.20 1.1.21 1.1.22 1.1.23 1.1.24 1.1.25 1.1.26 1.1.27 1.1.28 1.1.29 1.1.3 1.1.30 1.1.31 1.1.32 1.1.33 1.1.34 1.1.35 1.1.36 1.1.37 1.1.38 1.1.39 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.3 1.2.4 1.2.5
templates-patterns-collection / includes / Importers / WP / WP_Import.php
templates-patterns-collection / includes / Importers / WP Last commit date
Atomic_Wind_Meta_Handler.php 3 months ago Beaver_Data_Fix.php 5 years ago Beaver_ParserXML.php 5 years ago Elementor_Meta_Handler.php 3 months ago WP_Import.php 3 months ago WXR_Parser.php 5 years ago WXR_Parser_SimpleXML.php 4 years ago WXR_Parser_XML.php 5 years ago
WP_Import.php
991 lines
1 <?php
2 /**
3 * WordPress WXR Importer.
4 */
5
6 namespace TIOB\Importers\WP;
7
8 use TIOB\Importers\Cleanup\Active_State;
9 use TIOB\Importers\Helpers\Helper;
10 use TIOB\Importers\Helpers\Slug_Mapping;
11 use TIOB\Logger;
12 use WP_Error;
13 use WP_Importer;
14
15 /**
16 * WordPress Importer class for managing the import process of a WXR file
17 *
18 * @package templates-patterns-collection
19 */
20 class WP_Import extends WP_Importer {
21 use Helper;
22 /**
23 * @var Logger
24 */
25 private $logger;
26
27 public $max_wxr_version = 1.2; // max. supported WXR version
28 public $id; // WXR attachment ID
29 // information to import from WXR file
30 public $version;
31 public $posts = array();
32 public $terms = array();
33 public $categories = array();
34 public $tags = array();
35 public $base_url = '';
36 public $base_blog_url = '';
37 // mappings from old information to new
38 public $processed_posts = array();
39 public $processed_terms = array();
40 public $post_orphans = array();
41 public $processed_menu_items = array();
42 public $menu_item_orphans = array();
43 public $missing_menu_items = array();
44 public $fetch_attachments = true;
45 public $url_remap = array();
46 public $featured_images = array();
47 public $page_builder = null;
48
49 /**
50 * WP_Import constructor.
51 *
52 * @param string $page_builder the page builder used.
53 */
54 public function __construct( $page_builder = '' ) {
55 $this->page_builder = $page_builder;
56 require_once ABSPATH . 'wp-admin/includes/import.php';
57 require_once ABSPATH . 'wp-admin/includes/post.php';
58 require_once ABSPATH . 'wp-admin/includes/taxonomy.php';
59 }
60
61
62 /**
63 * The main controller for the actual import stage.
64 *
65 * @param string $file Path to the WXR file for importing
66 */
67 function import( $file ) {
68 $this->set_logger();
69 add_filter( 'import_post_meta_key', array( $this, 'is_valid_meta_key' ) );
70 add_filter( 'http_request_timeout', array( &$this, 'bump_request_timeout' ) );
71 $this->import_start( $file );
72 wp_suspend_cache_invalidation( true );
73 $this->process_categories();
74 $this->process_tags();
75 $this->process_terms();
76 add_filter( 'upload_mimes', array( $this, 'enable_svg_import' ) );
77 $this->process_posts();
78 remove_filter( 'upload_mimes', array( $this, 'enable_svg_import' ) );
79 wp_suspend_cache_invalidation( false );
80 // update incorrect/missing information in the DB
81 $this->backfill_parents();
82 $this->backfill_attachment_urls();
83 $this->remap_featured_images();
84 $this->import_end();
85 }
86
87 /**
88 * Filter the allowed mime types and add SVG support.
89 *
90 * @param array $mimes List of allowed mime types.
91 *
92 * @return array
93 */
94 public function enable_svg_import( $mimes ) {
95 $mimes['svg'] = 'image/svg+xml';
96 return $mimes;
97 }
98
99 /**
100 * Logger initialized.
101 */
102 private function set_logger() {
103 $this->logger = Logger::get_instance();
104 }
105
106 /**
107 * Parses the WXR file and prepares us for the task of processing parsed data
108 *
109 * @param string $file Path to the WXR file for importing
110 */
111 private function import_start( $file ) {
112 $this->logger->log( 'Import started.', 'success' );
113 if ( ! is_file( $file ) ) {
114 $this->logger->log( 'No file to import.' );
115 die();
116 }
117 $import_data = $this->parse( $file );
118 if ( is_wp_error( $import_data ) ) {
119 $this->logger->log( 'Error parsing WXR file.' );
120 die();
121 }
122 $this->version = $import_data['version'];
123 $this->posts = $import_data['posts'];
124 $this->terms = $import_data['terms'];
125 $this->categories = $import_data['categories'];
126 $this->tags = $import_data['tags'];
127 $this->base_url = esc_url( $import_data['base_url'] );
128 $this->base_blog_url = esc_url( $import_data['base_blog_url'] );
129 Slug_Mapping::register_source_url( $this->base_url );
130 Slug_Mapping::register_source_url( $this->base_blog_url );
131 wp_defer_term_counting( true );
132 wp_defer_comment_counting( true );
133 do_action( 'import_start' );
134 }
135
136 /**
137 * Performs post-import cleanup of files and the cache
138 */
139 private function import_end() {
140 $this->logger->log( "Cleaning up import with id {$this->id}...", 'progress' );
141 wp_import_cleanup( $this->id );
142 $this->logger->log( 'Done cleanup. Flushing cache...', 'progress' );
143 wp_cache_flush();
144 $this->logger->log( 'Flushed cache. Removing temporary data..', 'progress' );
145 foreach ( get_taxonomies() as $tax ) {
146 delete_option( "{$tax}_children" );
147 _get_term_hierarchy( $tax );
148 }
149 wp_defer_term_counting( false );
150 wp_defer_comment_counting( false );
151 $this->logger->log( 'Done importing', 'success' );
152 do_action( 'import_end' );
153 }
154
155 /**
156 * Create new categories based on import information
157 *
158 * Doesn't create a new category if its slug already exists
159 */
160 private function process_categories() {
161 $this->logger->log( 'Processing categories...', 'progress' );
162 $this->categories = apply_filters( 'wp_import_categories', $this->categories );
163 if ( empty( $this->categories ) ) {
164 $this->logger->log( 'No categories to process.', 'warning' );
165
166 return;
167 }
168 foreach ( $this->categories as $cat ) {
169 // if the category already exists leave it alone
170 $term_id = term_exists( $cat['category_nicename'], 'category' );
171 if ( $term_id ) {
172 if ( is_array( $term_id ) ) {
173 $term_id = $term_id['term_id'];
174 }
175 if ( isset( $cat['term_id'] ) ) {
176 $this->processed_terms[ intval( $cat['term_id'] ) ] = (int) $term_id;
177 }
178 continue;
179 }
180 $category_parent = empty( $cat['category_parent'] ) ? 0 : category_exists( $cat['category_parent'] );
181 $category_description = isset( $cat['category_description'] ) ? $cat['category_description'] : '';
182 $catarr = array(
183 'category_nicename' => $cat['category_nicename'],
184 'category_parent' => $category_parent,
185 'cat_name' => $cat['cat_name'],
186 'category_description' => $category_description,
187 );
188 $catarr = wp_slash( $catarr );
189 $id = wp_insert_category( $catarr );
190 if ( ! is_wp_error( $id ) ) {
191 if ( isset( $cat['term_id'] ) ) {
192 do_action( 'themeisle_cl_add_item_to_property_state', Active_State::CATEGORY_NSP, $id );
193 $this->processed_terms[ intval( $cat['term_id'] ) ] = $id;
194 }
195 } else {
196 $this->logger->log( $id->get_error_message() );
197 continue;
198 }
199 $this->process_termmeta( $cat, $id );
200 }
201 unset( $this->categories );
202 $this->logger->log( 'Processed categories.', 'success' );
203 }
204
205 /**
206 * Create new post tags based on import information
207 *
208 * Doesn't create a tag if its slug already exists
209 */
210 private function process_tags() {
211 $this->logger->log( 'Processing tags...', 'progress' );
212 $this->tags = apply_filters( 'wp_import_tags', $this->tags );
213 if ( empty( $this->tags ) ) {
214 $this->logger->log( 'No tags to process.', 'success' );
215
216 return;
217 }
218 foreach ( $this->tags as $tag ) {
219 // if the tag already exists leave it alone
220 $term_id = term_exists( $tag['tag_slug'], 'post_tag' );
221 if ( $term_id ) {
222 if ( is_array( $term_id ) ) {
223 $term_id = $term_id['term_id'];
224 }
225 if ( isset( $tag['term_id'] ) ) {
226 $this->processed_terms[ intval( $tag['term_id'] ) ] = (int) $term_id;
227 }
228 continue;
229 }
230 $tag = wp_slash( $tag );
231 $tag_desc = isset( $tag['tag_description'] ) ? $tag['tag_description'] : '';
232 $tagarr = array(
233 'slug' => $tag['tag_slug'],
234 'description' => $tag_desc,
235 );
236 $id = wp_insert_term( $tag['tag_name'], 'post_tag', $tagarr );
237 if ( ! is_wp_error( $id ) ) {
238 if ( isset( $tag['term_id'] ) ) {
239 do_action( 'themeisle_cl_add_item_to_property_state', Active_State::TAGS_NSP, $id['term_id'] );
240 $this->processed_terms[ intval( $tag['term_id'] ) ] = $id['term_id'];
241 }
242 } else {
243 $this->logger->log( "Failed to import post tag {$tag['tag_name']}" );
244 $this->logger->log( $id->get_error_message() );
245 continue;
246 }
247 $this->process_termmeta( $tag, $id['term_id'] );
248 }
249 $this->logger->log( 'Processed tags.', 'success' );
250 unset( $this->tags );
251 }
252
253 /**
254 * Create new terms based on import information
255 *
256 * Doesn't create a term its slug already exists
257 */
258 private function process_terms() {
259 $this->logger->log( 'Processing terms...', 'progress' );
260 $this->terms = apply_filters( 'wp_import_terms', $this->terms );
261 if ( empty( $this->terms ) ) {
262 $this->logger->log( 'No terms to process.', 'success' );
263
264 return;
265 }
266 foreach ( $this->terms as $term ) {
267 // if the term already exists in the correct taxonomy leave it alone
268 $term_id = term_exists( $term['slug'], $term['term_taxonomy'] );
269 if ( $term_id ) {
270 if ( is_array( $term_id ) ) {
271 $term_id = $term_id['term_id'];
272 }
273 if ( isset( $term['term_id'] ) ) {
274 $this->processed_terms[ intval( $term['term_id'] ) ] = (int) $term_id;
275 }
276 continue;
277 }
278 if ( empty( $term['term_parent'] ) ) {
279 $parent = 0;
280 } else {
281 $parent = term_exists( $term['term_parent'], $term['term_taxonomy'] );
282 if ( is_array( $parent ) ) {
283 $parent = $parent['term_id'];
284 }
285 }
286 $term = wp_slash( $term );
287 $description = isset( $term['term_description'] ) ? $term['term_description'] : '';
288 $termarr = array(
289 'slug' => $term['slug'],
290 'description' => $description,
291 'parent' => intval( $parent ),
292 );
293 $id = wp_insert_term( $term['term_name'], $term['term_taxonomy'], $termarr );
294 if ( ! is_wp_error( $id ) ) {
295 if ( isset( $term['term_id'] ) ) {
296 do_action(
297 'themeisle_cl_add_item_to_property_state',
298 Active_State::TERMS_NSP,
299 array(
300 'id' => $id['term_id'],
301 'taxonomy' => $term['term_taxonomy'],
302 )
303 );
304 $this->processed_terms[ intval( $term['term_id'] ) ] = $id['term_id'];
305 }
306 } else {
307 $this->logger->log( "Failed to import {$term['term_taxonomy']} {$term['term_name']}", 'warning' );
308 $this->logger->log( $id->get_error_message() );
309 continue;
310 }
311 $this->process_termmeta( $term, $id['term_id'] );
312 }
313
314 unset( $this->terms );
315 $this->logger->log( 'Processed terms.', 'success' );
316 }
317
318 /**
319 * Add metadata to imported term.
320 *
321 * @since 0.6.2
322 *
323 * @param array $term Term data from WXR import.
324 * @param int $term_id ID of the newly created term.
325 */
326 private function process_termmeta( $term, $term_id ) {
327 if ( ! isset( $term['termmeta'] ) ) {
328 $term['termmeta'] = array();
329 }
330 /**
331 * Filters the metadata attached to an imported term.
332 *
333 * @since 0.6.2
334 *
335 * @param array $termmeta Array of term meta.
336 * @param int $term_id ID of the newly created term.
337 * @param array $term Term data from the WXR import.
338 */
339 $term['termmeta'] = apply_filters( 'wp_import_term_meta', $term['termmeta'], $term_id, $term );
340 if ( empty( $term['termmeta'] ) ) {
341 return;
342 }
343 foreach ( $term['termmeta'] as $meta ) {
344 /**
345 * Filters the meta key for an imported piece of term meta.
346 *
347 * @since 0.6.2
348 *
349 * @param string $meta_key Meta key.
350 * @param int $term_id ID of the newly created term.
351 * @param array $term Term data from the WXR import.
352 */
353 $key = apply_filters( 'import_term_meta_key', $meta['key'], $term_id, $term );
354 if ( ! $key ) {
355 continue;
356 }
357 // Export gets meta straight from the DB so could have a serialized string
358 $value = maybe_unserialize( $meta['value'] );
359 add_term_meta( $term_id, $key, $value );
360 /**
361 * Fires after term meta is imported.
362 *
363 * @since 0.6.2
364 *
365 * @param int $term_id ID of the newly created term.
366 * @param string $key Meta key.
367 * @param mixed $value Meta value.
368 */
369 do_action( 'import_term_meta', $term_id, $key, $value );
370 }
371 }
372
373 /**
374 * Check whether the post type is a Pods plugin post type.
375 *
376 * @param string $post_type The post type.
377 *
378 * @return bool
379 */
380 private function is_post_type_of_pods_plugin( $post_type ) {
381 if ( ! ( function_exists( 'pods_api' ) && class_exists( 'Pod', true ) ) ) {
382 return false;
383 }
384 $api = pods_api();
385 $pods_post_types = $api->load_pods(
386 array(
387 'type' => 'post_type',
388 'refresh' => true,
389 )
390 );
391 foreach ( $pods_post_types as $pod_post_type ) {
392 if ( $pod_post_type['name'] === $post_type ) {
393 return true;
394 }
395 }
396 return false;
397 }
398
399 /**
400 * Create new posts based on import information
401 *
402 * Posts marked as having a parent which doesn't exist will become top level items.
403 * Doesn't create a new post if: the post type doesn't exist, the given post ID
404 * is already noted as imported or a post with the same title and date already exists.
405 * Note that new/updated terms, comments and meta are imported for the last of the above.
406 */
407 private function process_posts() {
408 $this->logger->log( 'Processing posts...', 'progress' );
409 $this->posts = apply_filters( 'wp_import_posts', $this->posts );
410 foreach ( $this->posts as $post ) {
411 $post = apply_filters( 'wp_import_post_data_raw', $post );
412 if ( ! post_type_exists( $post['post_type'] ) ) {
413 if ( ! $this->is_post_type_of_pods_plugin( $post['post_type'] ) ) {
414 $this->logger->log( "Failed to import {$post['post_title']}. Invalid post type {$post['post_type']}" );
415 do_action( 'wp_import_post_exists', $post );
416 continue;
417 }
418 }
419 if ( isset( $this->processed_posts[ $post['post_id'] ] ) && ! empty( $post['post_id'] ) ) {
420 continue;
421 }
422 if ( $post['status'] == 'auto-draft' ) {
423 continue;
424 }
425 if ( 'nav_menu_item' == $post['post_type'] ) {
426 $this->process_menu_item( $post );
427 continue;
428 }
429 $post_type_object = get_post_type_object( $post['post_type'] );
430 $post_exists = post_exists( $post['post_title'], '', $post['post_date'] );
431 /**
432 * Filter ID of the existing post corresponding to post currently importing.
433 *
434 * Return 0 to force the post to be imported. Filter the ID to be something else
435 * to override which existing post is mapped to the imported post.
436 *
437 * @see post_exists()
438 * @since 0.6.2
439 *
440 * @param int $post_exists Post ID, or 0 if post did not exist.
441 * @param array $post The post array to be inserted.
442 */
443 $post_exists = apply_filters( 'wp_import_existing_post', $post_exists, $post );
444 if ( $post_exists && get_post_type( $post_exists ) == $post['post_type'] ) {
445 $this->logger->log( $post_type_object->labels->singular_name . ' ' . esc_html( $post['post_title'] ) . ' already exists.', 'success' );
446 $comment_post_id = $post_id = $post_exists;
447 $this->processed_posts[ intval( $post['post_id'] ) ] = intval( $post_exists );
448 } else {
449 $post_parent = (int) $post['post_parent'];
450 if ( $post_parent ) {
451 // if we already know the parent, map it to the new local ID
452 if ( isset( $this->processed_posts[ $post_parent ] ) ) {
453 $post_parent = $this->processed_posts[ $post_parent ];
454 // otherwise record the parent for later
455 } else {
456 $this->post_orphans[ intval( $post['post_id'] ) ] = $post_parent;
457 $post_parent = 0;
458 }
459 }
460 $author = (int) get_current_user_id();
461 $postdata = array(
462 'import_id' => $post['post_id'],
463 'post_author' => $author,
464 'post_date' => $post['post_date'],
465 'post_date_gmt' => $post['post_date_gmt'],
466 'post_content' => $post['post_content'],
467 'post_excerpt' => $post['post_excerpt'],
468 'post_title' => $post['post_title'],
469 'post_status' => $post['status'],
470 'post_name' => $post['post_name'],
471 'comment_status' => $post['comment_status'],
472 'ping_status' => $post['ping_status'],
473 'guid' => $post['guid'],
474 'post_parent' => $post_parent,
475 'menu_order' => $post['menu_order'],
476 'post_type' => $post['post_type'],
477 'post_password' => $post['post_password'],
478 );
479 $original_post_id = $post['post_id'];
480 $postdata = apply_filters( 'wp_import_post_data_processed', $postdata, $post );
481 $postdata = wp_slash( $postdata );
482 if ( 'attachment' == $postdata['post_type'] ) {
483 $remote_url = ! empty( $post['attachment_url'] ) ? $post['attachment_url'] : $post['guid'];
484 // try to use _wp_attached file for upload folder placement to ensure the same location as the export site
485 // e.g. location is 2003/05/image.jpg but the attachment post_date is 2010/09, see media_handle_upload()
486 $postdata['upload_date'] = $post['post_date'];
487 if ( isset( $post['postmeta'] ) ) {
488 foreach ( $post['postmeta'] as $meta ) {
489 if ( $meta['key'] == '_wp_attached_file' ) {
490 if ( preg_match( '%^[0-9]{4}/[0-9]{2}%', $meta['value'], $matches ) ) {
491 $postdata['upload_date'] = $matches[0];
492 }
493 break;
494 }
495 }
496 }
497 $comment_post_id = $post_id = $this->process_attachment( $postdata, $remote_url );
498 do_action( 'themeisle_cl_add_item_to_property_state', Active_State::ATTACHMENT_NSP, $post_id );
499 } else {
500 $this->logger->log( "Inserting {$postdata['post_type']}: {$postdata['post_title']}.", 'progress' );
501 $postdata['post_content'] = apply_filters( 'tpc_post_content_processed_terms', $postdata['post_content'], $this->processed_terms );
502 $postdata['post_content'] = apply_filters( 'tpc_post_content_before_insert', $postdata['post_content'], $this->base_blog_url );
503 $comment_post_id = $post_id = wp_insert_post( $postdata, true );
504 $this->logger->log( "Done inserting {$postdata['post_type']}: {$postdata['post_title']}.", 'success' );
505 do_action( 'wp_import_insert_post', $post_id, $original_post_id, $postdata, $post );
506 do_action( 'themeisle_cl_add_item_to_property_state', Active_State::POSTS_NSP, $post_id );
507 }
508 if ( is_wp_error( $post_id ) ) {
509 $this->logger->log( "Failed to import {$post_type_object->labels->singular_name} {$post['post_title']}. \n {$post_id->get_error_message()}" );
510 continue;
511 }
512 if ( $post['is_sticky'] == 1 ) {
513 stick_post( $post_id );
514 }
515 }
516 // map pre-import ID to local ID
517 $this->processed_posts[ intval( $post['post_id'] ) ] = (int) $post_id;
518 if ( ! isset( $post['terms'] ) ) {
519 $post['terms'] = array();
520 }
521 $post['terms'] = apply_filters( 'wp_import_post_terms', $post['terms'], $post_id, $post );
522 // add categories, tags and other terms
523 if ( ! empty( $post['terms'] ) ) {
524 $terms_to_set = array();
525 foreach ( $post['terms'] as $term ) {
526 // back compat with WXR 1.0 map 'tag' to 'post_tag'
527 $taxonomy = ( 'tag' == $term['domain'] ) ? 'post_tag' : $term['domain'];
528 $term_exists = term_exists( $term['slug'], $taxonomy );
529 $term_id = is_array( $term_exists ) ? $term_exists['term_id'] : $term_exists;
530 if ( ! $term_id ) {
531 $t = wp_insert_term( $term['name'], $taxonomy, array( 'slug' => $term['slug'] ) );
532 if ( ! is_wp_error( $t ) ) {
533 $term_id = $t['term_id'];
534 do_action( 'wp_import_insert_term', $t, $term, $post_id, $post );
535 } else {
536 $this->logger->log( "Failed to import {$taxonomy} {$term['name']}", 'success' );
537 $this->logger->log( $t->get_error_message() );
538 do_action( 'wp_import_insert_term_failed', $t, $term, $post_id, $post );
539 continue;
540 }
541 }
542 $terms_to_set[ $taxonomy ][] = intval( $term_id );
543 }
544 foreach ( $terms_to_set as $tax => $ids ) {
545 $tt_ids = wp_set_post_terms( $post_id, $ids, $tax );
546 do_action( 'wp_import_set_post_terms', $tt_ids, $ids, $tax, $post_id, $post );
547 }
548 unset( $post['terms'], $terms_to_set );
549 }
550 if ( ! isset( $post['comments'] ) ) {
551 $post['comments'] = array();
552 }
553 $post['comments'] = apply_filters( 'wp_import_post_comments', $post['comments'], $post_id, $post );
554 if ( ! empty( $post['comments'] ) ) {
555 foreach ( $post['comments'] as $comment ) {
556 $comment['comment_post_ID'] = $post_id;
557 $comment['comment_meta'] = array_combine( array_column( $comment['commentmeta'], 'key' ), array_column( $comment['commentmeta'], 'value' ) );
558 unset( $comment['commentmeta'] );
559 $comment_id = wp_insert_comment( $comment );
560 if ( $comment_id === false ) {
561 $this->logger->log( "Could not import comment for {$post_id}." );
562 continue;
563 }
564 do_action( 'themeisle_cl_add_item_to_property_state', Active_State::COMMENTS_NSP, $comment_id );
565 }
566 }
567
568 if ( ! isset( $post['postmeta'] ) ) {
569 $post['postmeta'] = array();
570 }
571 $post['postmeta'] = apply_filters( 'wp_import_post_meta', $post['postmeta'], $post_id, $post );
572 // add/update post meta
573 if ( ! empty( $post['postmeta'] ) ) {
574 foreach ( $post['postmeta'] as $meta ) {
575 $key = apply_filters( 'import_post_meta_key', $meta['key'], $post_id, $post );
576 $value = false;
577 if ( '_edit_last' == $key ) {
578 $key = false;
579 }
580 if ( $key ) {
581 // export gets meta straight from the DB so could have a serialized string
582 if ( ! $value ) {
583 $value = maybe_unserialize( $meta['value'] );
584 }
585 if ( $key === '_elementor_data' ) {
586 $this->logger->log( 'Filtering elementor meta...', 'progress' );
587 $meta_handler = new Elementor_Meta_Handler( $value, $this->base_blog_url );
588 $meta_handler->filter_meta();
589 $this->logger->log( 'Filtered elementor meta.', 'success' );
590 } else {
591 $value = Slug_Mapping::rewrite_value( $value );
592 }
593 if ( $key === '_atomic_wind_css' ) {
594 $meta_handler = new Atomic_Wind_Meta_Handler( $value );
595 $meta_handler->filter_meta();
596 }
597 if ( in_array(
598 $key,
599 array(
600 'tve_custom_css',
601 'tve_content_before_more',
602 'tve_updated_post',
603 )
604 ) ) {
605 $value = $this->replace_image_urls( $value );
606 }
607 update_post_meta( $post_id, $key, $value );
608 do_action( 'import_post_meta', $post_id, $key, $value );
609 // if the post has a featured image, take note of this in case of remap
610 if ( '_thumbnail_id' == $key ) {
611 $this->featured_images[ $post_id ] = (int) $value;
612 }
613 }
614 }
615 }
616 }
617 unset( $this->posts );
618 $this->logger->log( 'Processed posts.', 'success' );
619 }
620
621 /**
622 * Attempt to create a new menu item from import data
623 *
624 * Fails for draft, orphaned menu items and those without an associated nav_menu
625 * or an invalid nav_menu term. If the post type or term object which the menu item
626 * represents doesn't exist then the menu item will not be imported (waits until the
627 * end of the import to retry again before discarding).
628 *
629 * @param array $item Menu item details from WXR file
630 */
631 private function process_menu_item( $item ) {
632 $this->logger->log( "Processing single menu item '{$item['post_title']}'...", 'progress' );
633 // skip draft, orphaned menu items
634 if ( 'draft' == $item['status'] ) {
635 return;
636 }
637 $menu_slug = false;
638 if ( isset( $item['terms'] ) ) {
639 // loop through terms, assume first nav_menu term is correct menu
640 foreach ( $item['terms'] as $term ) {
641 if ( 'nav_menu' == $term['domain'] ) {
642 $menu_slug = $term['slug'];
643 break;
644 }
645 }
646 }
647 // no nav_menu term associated with this menu item
648 if ( ! $menu_slug ) {
649 $this->logger->log( 'Menu item skipped. Missing slug.', 'error' );
650
651 return;
652 }
653 $menu_id = term_exists( $menu_slug, 'nav_menu' );
654 if ( ! $menu_id ) {
655 $this->logger->log( "Menu item invalid slug: {$menu_slug}", 'success' );
656
657 return;
658 } else {
659 $menu_id = is_array( $menu_id ) ? $menu_id['term_id'] : $menu_id;
660 }
661 $nv_mm_options = '';
662 foreach ( $item['postmeta'] as $meta ) {
663 ${$meta['key']} = $meta['value'];
664 }
665 if ( 'taxonomy' == $_menu_item_type && isset( $this->processed_terms[ intval( $_menu_item_object_id ) ] ) ) {
666 $_menu_item_object_id = $this->processed_terms[ intval( $_menu_item_object_id ) ];
667 } elseif ( 'post_type' == $_menu_item_type && isset( $this->processed_posts[ intval( $_menu_item_object_id ) ] ) ) {
668 $_menu_item_object_id = $this->processed_posts[ intval( $_menu_item_object_id ) ];
669 } elseif ( 'custom' != $_menu_item_type ) {
670 // associated object is missing or not imported yet, we'll retry later
671 $this->missing_menu_items[] = $item;
672
673 return;
674 }
675 if ( isset( $this->processed_menu_items[ intval( $_menu_item_menu_item_parent ) ] ) ) {
676 $_menu_item_menu_item_parent = $this->processed_menu_items[ intval( $_menu_item_menu_item_parent ) ];
677 } elseif ( $_menu_item_menu_item_parent ) {
678 $this->menu_item_orphans[ intval( $item['post_id'] ) ] = (int) $_menu_item_menu_item_parent;
679 $_menu_item_menu_item_parent = 0;
680 }
681 // wp_update_nav_menu_item expects CSS classes as a space separated string
682 $_menu_item_classes = maybe_unserialize( $_menu_item_classes );
683 if ( is_array( $_menu_item_classes ) ) {
684 $_menu_item_classes = implode( ' ', $_menu_item_classes );
685 }
686 $args = array(
687 'menu-item-object-id' => $_menu_item_object_id,
688 'menu-item-object' => $_menu_item_object,
689 'menu-item-parent-id' => $_menu_item_menu_item_parent,
690 'menu-item-position' => intval( $item['menu_order'] ),
691 'menu-item-type' => $_menu_item_type,
692 'menu-item-title' => $item['post_title'],
693 'menu-item-url' => $_menu_item_url,
694 'menu-item-description' => $item['post_content'],
695 'menu-item-attr-title' => $item['post_excerpt'],
696 'menu-item-target' => $_menu_item_target,
697 'menu-item-classes' => $_menu_item_classes,
698 'menu-item-xfn' => $_menu_item_xfn,
699 'menu-item-status' => $item['status'],
700 );
701 $args = apply_filters( 'wp_import_nav_menu_item_args', $args, $this->base_blog_url );
702 $existing = wp_get_nav_menu_items( $menu_id );
703 foreach ( $existing as $existing_item ) {
704 if ( $args['menu-item-url'] === $existing_item->url && trim( $args['menu-item-title'] ) === $existing_item->title ) {
705 $this->logger->log( 'Menu item already exists.', 'success' );
706
707 return;
708 }
709 }
710
711 $id = wp_update_nav_menu_item( $menu_id, 0, $args );
712 if ( $id && ! is_wp_error( $id ) ) {
713 if ( ! empty( $nv_mm_options ) ) {
714 update_post_meta( $id, 'nv_mm_options', $nv_mm_options );
715 }
716 $this->processed_menu_items[ intval( $item['post_id'] ) ] = (int) $id;
717 }
718 $this->logger->log( 'Processed single menu item.', 'success' );
719 }
720
721 /**
722 * If fetching attachments is enabled then attempt to create a new attachment
723 *
724 * @param array $post Attachment post details from WXR
725 * @param string $url URL to fetch attachment from
726 *
727 * @return int|WP_Error Post ID on success, WP_Error otherwise
728 */
729 private function process_attachment( $post, $url ) {
730 $this->logger->log( "Processing attachment: {$url}...", 'progress' );
731 if ( ! $this->fetch_attachments ) {
732 $this->logger->log( 'Fetching attachments is not enabled', 'error' );
733 }
734 // if the URL is absolute, but does not contain address, then upload it assuming base_site_url
735 if ( preg_match( '|^/[\w\W]+$|', $url ) ) {
736 $url = rtrim( $this->base_url, '/' ) . $url;
737 }
738 $upload = $this->fetch_remote_file( $url, $post );
739 if ( is_wp_error( $upload ) ) {
740 $this->logger->log( $upload );
741
742 return $upload;
743 }
744 $info = wp_check_filetype( $upload['file'] );
745 if ( $info ) {
746 $post['post_mime_type'] = $info['type'];
747 } else {
748 $error = new WP_Error( 'attachment_processing_error', 'Invalid file type' );
749 $this->logger->log( $error->get_error_message() );
750
751 return $error;
752 }
753 $post['guid'] = $upload['url'];
754 // as per wp-admin/includes/upload.php
755 $this->logger->log( 'Inserting attachment...', 'progress' );
756 $post_id = wp_insert_attachment( $post, $upload['file'] );
757 $this->logger->log( 'Attachment inserted', 'success' );
758 wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) );
759 // remap resized image URLs, works by stripping the extension and remapping the URL stub.
760 if ( preg_match( '!^image/!', $info['type'] ) ) {
761 $parts = pathinfo( $url );
762 $name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2
763 $parts_new = pathinfo( $upload['url'] );
764 $name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" );
765 $this->url_remap[ $parts['dirname'] . '/' . $name ] = $parts_new['dirname'] . '/' . $name_new;
766 }
767 $this->logger->log( 'Processed attachment.', 'success' );
768
769 return $post_id;
770 }
771
772 /**
773 * Attempt to download a remote file attachment
774 *
775 * @param string $url URL of item to fetch
776 * @param array $post Attachment details
777 *
778 * @return array|WP_Error Local file location details on success, WP_Error otherwise
779 */
780 private function fetch_remote_file( $url, $post ) {
781 $this->logger->log( "Fetching attachment from url: {$url}...", 'progress' );
782 // extract the file name and extension from the url
783 $file_name = basename( $url );
784 // get placeholder file in the upload dir with a unique, sanitized filename
785 $upload = wp_upload_bits( $file_name, 0, '', $post['upload_date'] );
786 if ( $upload['error'] ) {
787 return new WP_Error( 'upload_dir_error', $upload['error'] );
788 }
789 // fetch the remote url and write it to the placeholder file
790 $remote_response = wp_safe_remote_get(
791 $url,
792 array(
793 'timeout' => 300,
794 'stream' => true,
795 'filename' => $upload['file'],
796 )
797 );
798 $headers = wp_remote_retrieve_headers( $remote_response );
799 // request failed
800 if ( ! $headers ) {
801 @unlink( $upload['file'] );
802 $error = new WP_Error( 'import_file_error', 'Remote server did not respond' );
803 $this->logger->log( $error->get_error_message() );
804
805 return $error;
806 }
807 $remote_response_code = wp_remote_retrieve_response_code( $remote_response );
808 // make sure the fetch was successful
809 if ( $remote_response_code != '200' ) {
810 @unlink( $upload['file'] );
811 $error = new WP_Error( 'import_file_error', sprintf( 'Remote server returned error response %1$d %2$s', esc_html( $remote_response_code ), get_status_header_desc( $remote_response_code ) ) );
812 $this->logger->log( $error->get_error_message() );
813
814 return $error;
815 }
816 $filesize = filesize( $upload['file'] );
817 if ( isset( $headers['content-length'] ) && $filesize != $headers['content-length'] ) {
818 @unlink( $upload['file'] );
819 $error = new WP_Error( 'import_file_error', 'Remote file is incorrect size' );
820 $this->logger->log( $error->get_error_message() );
821
822 return $error;
823 }
824 if ( 0 == $filesize ) {
825 @unlink( $upload['file'] );
826 $error = new WP_Error( 'import_file_error', 'Zero size file downloaded' );
827 $this->logger->log( $error->get_error_message() );
828
829 return $error;
830 }
831 $max_size = (int) $this->max_attachment_size();
832 if ( ! empty( $max_size ) && $filesize > $max_size ) {
833 @unlink( $upload['file'] );
834 $error = new WP_Error( 'import_file_error', sprintf( 'Remote file is too large, limit is %s', size_format( $max_size ) ) );
835 $this->logger->log( $error->get_error_message() );
836
837 return $error;
838 }
839 // keep track of the old and new urls so we can substitute them later
840 $this->url_remap[ $url ] = $upload['url'];
841 $this->url_remap[ $post['guid'] ] = $upload['url']; // r13735, really needed?
842 // keep track of the destination if the remote url is redirected somewhere else
843 if ( isset( $headers['x-final-location'] ) && $headers['x-final-location'] != $url ) {
844 $this->url_remap[ $headers['x-final-location'] ] = $upload['url'];
845 }
846
847 $this->logger->log( 'Fetched remote attachment.', 'success' );
848
849 return $upload;
850 }
851
852 /**
853 * Attempt to associate posts and menu items with previously missing parents
854 *
855 * An imported post's parent may not have been imported when it was first created
856 * so try again. Similarly for child menu items and menu items which were missing
857 * the object (e.g. post) they represent in the menu
858 */
859 function backfill_parents() {
860 global $wpdb;
861 // find parents for post orphans
862 foreach ( $this->post_orphans as $child_id => $parent_id ) {
863 $local_child_id = $local_parent_id = false;
864 if ( isset( $this->processed_posts[ $child_id ] ) ) {
865 $local_child_id = $this->processed_posts[ $child_id ];
866 }
867 if ( isset( $this->processed_posts[ $parent_id ] ) ) {
868 $local_parent_id = $this->processed_posts[ $parent_id ];
869 }
870 if ( $local_child_id && $local_parent_id ) {
871 $wpdb->update( $wpdb->posts, array( 'post_parent' => $local_parent_id ), array( 'ID' => $local_child_id ), '%d', '%d' );
872 clean_post_cache( $local_child_id );
873 }
874 }
875 // all other posts/terms are imported, retry menu items with missing associated object
876 $missing_menu_items = $this->missing_menu_items;
877 foreach ( $missing_menu_items as $item ) {
878 $this->process_menu_item( $item );
879 }
880 // find parents for menu item orphans
881 foreach ( $this->menu_item_orphans as $child_id => $parent_id ) {
882 $local_child_id = $local_parent_id = 0;
883 if ( isset( $this->processed_menu_items[ $child_id ] ) ) {
884 $local_child_id = $this->processed_menu_items[ $child_id ];
885 }
886 if ( isset( $this->processed_menu_items[ $parent_id ] ) ) {
887 $local_parent_id = $this->processed_menu_items[ $parent_id ];
888 }
889 if ( $local_child_id && $local_parent_id ) {
890 update_post_meta( $local_child_id, '_menu_item_menu_item_parent', (int) $local_parent_id );
891 }
892 }
893 }
894
895 /**
896 * Use stored mapping information to update old attachment URLs
897 */
898 function backfill_attachment_urls() {
899 global $wpdb;
900 // make sure we do the longest urls first, in case one is a substring of another
901 uksort( $this->url_remap, array( &$this, 'cmpr_strlen' ) );
902 foreach ( $this->url_remap as $from_url => $to_url ) {
903 // remap urls in post_content
904 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url ) );
905 // remap enclosure urls
906 $result = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url ) );
907 }
908 }
909
910 /**
911 * Update _thumbnail_id meta to new, imported attachment IDs
912 */
913 function remap_featured_images() {
914 // cycle through posts that have a featured image
915 foreach ( $this->featured_images as $post_id => $value ) {
916 if ( isset( $this->processed_posts[ $value ] ) ) {
917 $new_id = $this->processed_posts[ $value ];
918 // only update if there's a difference
919 if ( $new_id != $value ) {
920 update_post_meta( $post_id, '_thumbnail_id', $new_id );
921 }
922 }
923 }
924 }
925
926 /**
927 * Parse a WXR file
928 *
929 * @param string $file Path to WXR file for parsing
930 *
931 * @return array Information gathered from the WXR file
932 */
933 private function parse( $file ) {
934 $this->logger->log( 'Parsing XML file.', 'success' );
935 $parser = new WXR_Parser( $this->page_builder );
936
937 return $parser->parse( $file );
938 }
939
940 /**
941 * Decide if the given meta key maps to information we will want to import
942 *
943 * @param string $key The meta key to check
944 *
945 * @return string|bool The key if we do want to import, false if not
946 */
947 public function is_valid_meta_key( $key ) {
948 // skip attachment metadata since we'll regenerate it from scratch
949 // skip _edit_lock as not relevant for import
950 if ( in_array( $key, array( '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ) ) ) {
951 return false;
952 }
953
954 return $key;
955 }
956
957 /**
958 * Decide whether or not the importer should attempt to download attachment files.
959 * Default is true, can be filtered via import_allow_fetch_attachments. The choice
960 * made at the import options screen must also be true, false here hides that checkbox.
961 *
962 * @return bool True if downloading attachments is allowed
963 */
964 function allow_fetch_attachments() {
965 return apply_filters( 'import_allow_fetch_attachments', true );
966 }
967
968 /**
969 * Decide what the maximum file size for downloaded attachments is.
970 * Default is 0 (unlimited), can be filtered via import_attachment_size_limit
971 *
972 * @return int Maximum attachment file size to import
973 */
974 private function max_attachment_size() {
975 return apply_filters( 'import_attachment_size_limit', 0 );
976 }
977
978 /**
979 * Added to http_request_timeout filter to force timeout at 60 seconds during import
980 * @return int 60
981 */
982 public function bump_request_timeout( $val ) {
983 return 60;
984 }
985
986 // return the difference in length between two strings
987 public function cmpr_strlen( $a, $b ) {
988 return strlen( $b ) - strlen( $a );
989 }
990 }
991