PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.0.1
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.0.1
3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.10 All 111 releases
templately / includes / Core / Importer / WPImport.php

WPImport.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.0.1, at includes/Core/Importer/WPImport.php

1,450 lines 46.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Core\Importer;
4
5 use Templately\Core\Importer\Parsers\WXR_Parser;
6 use Templately\Core\Importer\Utils\Utils;
7 use Templately\Utils\Helper;
8 use WP_Error;
9 use WP_Importer;
10 use function wp_import_cleanup;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly.
14 }
15
16 /**
17 * Originally made by WordPress part of WordPress/Importer.
18 * https://plugins.trac.wordpress.org/browser/wordpress-importer/trunk/class-wp-import.php
19 *
20 * What was done ( by Elementor):
21 * Reformat of the code.
22 * Changed text domain.
23 * Changed methods visibility.
24 * Changed method from `get_authors_from_import` to `set_authors_from_import`.
25 * Changed method from `get_author_mapping` to `set_author_mapping`.
26 * Removed use of '$_POST' the input 'options' will be passed via constructor args.
27 * Removed echos, UI and print methods, all echos replaced with `$this->output` append.
28 * Removed `die` ( exit(s) ).
29 *
30 * What was done ( by Templately):
31 * Add Action For Every Part Of Import for SSE
32 */
33
34 if ( ! class_exists( 'WP_Importer' ) ) {
35 $class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php';
36
37 if ( file_exists( $class_wp_importer ) ) {
38 require $class_wp_importer;
39 }
40 }
41
42 if ( ! function_exists( 'wp_import_cleanup' ) ) {
43 $wp_import = ABSPATH . 'wp-admin/includes/import.php';
44
45 if ( file_exists( $wp_import ) ) {
46 require $wp_import;
47 }
48 }
49
50 class WPImport extends WP_Importer {
51 const DEFAULT_BUMP_REQUEST_TIMEOUT = 60;
52 const DEFAULT_ALLOW_CREATE_USERS = true;
53 const DEFAULT_IMPORT_ATTACHMENT_SIZE_LIMIT = 0; // 0 = unlimited.
54
55 /**
56 * @var string
57 */
58 private $requested_file_path;
59
60 /**
61 * @var array
62 */
63 private $args;
64
65 /**
66 * @var array
67 */
68 private $output = [
69 'status' => 'failed',
70 'errors' => [],
71 ];
72
73 /*
74 * WXR attachment ID
75 */
76 private $id;
77
78 // Information to import from WXR file.
79 private $version;
80 private $authors = [];
81 public $posts = [];
82 public $terms = [];
83 private $base_url = '';
84 private $page_on_front;
85 private $base_blog_url = '';
86
87 // Mappings from old information to new.
88 public $processed_taxonomies;
89 public $processed_terms = [];
90 public $processed_posts = [];
91 private $processed_authors = [];
92 private $author_mapping = [];
93 private $processed_menu_items = [];
94 private $post_orphans = [];
95 private $menu_item_orphans = [];
96 private $mapped_terms_slug = [];
97
98 private $fetch_attachments = false;
99 private $url_remap = [];
100 private $featured_images = [];
101
102 /**
103 * @var array[] [meta_key => meta_value] Meta value that should be set for every imported post.
104 */
105 private $posts_meta = [];
106
107 /**
108 * @var array[] [meta_key => meta_value] Meta value that should be set for every imported term.
109 */
110 private $terms_meta = [];
111
112 /**
113 * Parses filename from a Content-Disposition header value.
114 *
115 * As per RFC6266:
116 *
117 * content-disposition = "Content-Disposition" ":"
118 * disposition-type *( ";" disposition-parm )
119 *
120 * disposition-type = "inline" | "attachment" | disp-ext-type
121 * ; case-insensitive
122 * disp-ext-type = token
123 *
124 * disposition-parm = filename-parm | disp-ext-parm
125 *
126 * filename-parm = "filename" "=" value
127 * | "filename*" "=" ext-value
128 *
129 * disp-ext-parm = token "=" value
130 * | ext-token "=" ext-value
131 * ext-token = <the characters in token, followed by "*">
132 *
133 * @param string[] $disposition_header List of Content-Disposition header values.
134 *
135 * @return string|null Filename if available, or null if not found.
136 * @link http://tools.ietf.org/html/rfc2388
137 * @link http://tools.ietf.org/html/rfc6266
138 *
139 * @see WP_REST_Attachments_Controller::get_filename_from_disposition()
140 *
141 */
142 protected static function get_filename_from_disposition( $disposition_header ) {
143 // Get the filename.
144 $filename = null;
145
146 foreach ( $disposition_header as $value ) {
147 $value = trim( $value );
148
149 if ( strpos( $value, ';' ) === false ) {
150 continue;
151 }
152
153 list( $type, $attr_parts ) = explode( ';', $value, 2 );
154
155 $attr_parts = explode( ';', $attr_parts );
156 $attributes = [];
157
158 foreach ( $attr_parts as $part ) {
159 if ( strpos( $part, '=' ) === false ) {
160 continue;
161 }
162
163 list( $key, $value ) = explode( '=', $part, 2 );
164
165 $attributes[ trim( $key ) ] = trim( $value );
166 }
167
168 if ( empty( $attributes['filename'] ) ) {
169 continue;
170 }
171
172 $filename = trim( $attributes['filename'] );
173
174 // Unquote quoted filename, but after trimming.
175 if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, -1, 1 ) === '"' ) {
176 $filename = substr( $filename, 1, -1 );
177 }
178 }
179
180 return $filename;
181 }
182
183 /**
184 * Retrieves file extension by mime type.
185 *
186 * @param string $mime_type Mime type to search extension for.
187 *
188 * @return string|null File extension if available, or null if not found.
189 */
190 protected static function get_file_extension_by_mime_type( $mime_type ) {
191 static $map = null;
192
193 if ( is_array( $map ) ) {
194 return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null;
195 }
196
197 $mime_types = wp_get_mime_types();
198 $map = array_flip( $mime_types );
199
200 // Some types have multiple extensions, use only the first one.
201 foreach ( $map as $type => $extensions ) {
202 $map[ $type ] = strtok( $extensions, '|' );
203 }
204
205 return isset( $map[ $mime_type ] ) ? $map[ $mime_type ] : null;
206 }
207
208 /**
209 * The main controller for the actual import stage.
210 *
211 * @param string $file Path to the WXR file for importing
212 */
213 private function import( $file ) {
214 add_filter( 'import_post_meta_key', function ( $key ) {
215 return $this->is_valid_meta_key( $key );
216 } );
217 add_filter( 'http_request_timeout', function () {
218 return self::DEFAULT_BUMP_REQUEST_TIMEOUT;
219 } );
220
221 if ( ! $this->import_start( $file ) ) {
222 return;
223 }
224
225
226 $this->set_author_mapping();
227
228 wp_suspend_cache_invalidation( true );
229 $imported_summary = [
230 'terms' => $this->process_terms(),
231 'posts' => $this->process_posts(),
232 ];
233 wp_suspend_cache_invalidation( false );
234
235 // Update incorrect/missing information in the DB.
236 $this->backfill_parents();
237 $this->backfill_attachment_urls();
238 $this->remap_featured_images();
239
240 $this->import_end();
241
242 $is_some_succeed = false;
243 foreach ( $imported_summary as $item ) {
244 if ( $item > 0 ) {
245 $is_some_succeed = true;
246 break;
247 }
248 }
249
250 if ( $is_some_succeed ) {
251 $this->output['status'] = 'success';
252 $this->output['summary'] = $imported_summary;
253 }
254 }
255
256 /**
257 * Parses the WXR file and prepares us for the task of processing parsed data.
258 *
259 * @param string $file Path to the WXR file for importing
260 */
261 private function import_start( string $file ): bool {
262 if ( ! is_file( $file ) ) {
263 $this->output['errors'] = [ esc_html__( 'The file does not exist, please try again.', 'elementor' ) ];
264
265 return false;
266 }
267
268 $import_data = $this->parse( $file );
269
270 if ( is_wp_error( $import_data ) ) {
271 /**
272 * @var WP_Error $import_data ;
273 */
274 $this->output['errors'] = [ $import_data->get_error_message() ];
275
276 return false;
277 }
278 $this->version = $import_data['version'];
279 $this->set_authors_from_import( $import_data );
280 $this->posts = $import_data['posts'];
281 $this->terms = $import_data['terms'];
282 $this->base_url = esc_url( $import_data['base_url'] );
283 $this->base_blog_url = esc_url( $import_data['base_blog_url'] );
284 $this->page_on_front = $import_data['page_on_front'];
285
286 wp_defer_term_counting( true );
287 wp_defer_comment_counting( true );
288
289 do_action( 'import_start', $this );
290
291 return true;
292 }
293
294 /**
295 * Performs post-import cleanup of files and the cache
296 */
297 private function import_end() {
298 wp_import_cleanup( $this->id );
299
300 wp_cache_flush();
301
302 foreach ( get_taxonomies() as $tax ) {
303 delete_option( "{$tax}_children" );
304 _get_term_hierarchy( $tax );
305 }
306
307 wp_defer_term_counting( false );
308 wp_defer_comment_counting( false );
309
310 do_action( 'import_end' );
311 }
312
313 /**
314 * Retrieve authors from parsed WXR data and set it to `$this->>authors`.
315 *
316 * Uses the provided author information from WXR 1.1 files
317 * or extracts info from each post for WXR 1.0 files
318 *
319 * @param array $import_data Data returned by a WXR parser
320 */
321 private function set_authors_from_import( $import_data ) {
322 if ( ! empty( $import_data['authors'] ) ) {
323 $this->authors = $import_data['authors'];
324 // No author information, grab it from the posts.
325 } else {
326 foreach ( $import_data['posts'] as $post ) {
327 $login = sanitize_user( $post['post_author'], true );
328
329 if ( empty( $login ) ) {
330 /* translators: %s: Post author. */
331 $this->output['errors'][] = sprintf( esc_html__( 'Failed to import author %s. Their posts will be attributed to the current user.', 'elementor' ), $post['post_author'] );
332 continue;
333 }
334
335 if ( ! isset( $this->authors[ $login ] ) ) {
336 $this->authors[ $login ] = [
337 'author_login' => $login,
338 'author_display_name' => $post['post_author'],
339 ];
340 }
341 }
342 }
343 }
344
345 /**
346 * Map old author logins to local user IDs based on decisions made
347 * in import options form. Can map to an existing user, create a new user
348 * or falls back to the current user in case of error with either of the previous
349 */
350 private function set_author_mapping() {
351 if ( ! isset( $this->args['imported_authors'] ) ) {
352 return;
353 }
354
355 $create_users = apply_filters( 'import_allow_create_users', self::DEFAULT_ALLOW_CREATE_USERS );
356
357 foreach ( (array) $this->args['imported_authors'] as $i => $old_login ) {
358 // Multisite adds strtolower to sanitize_user. Need to sanitize here to stop breakage in process_posts.
359 $santized_old_login = sanitize_user( $old_login, true );
360 $old_id = isset( $this->authors[ $old_login ]['author_id'] ) ? (int) $this->authors[ $old_login ]['author_id'] : false;
361
362 if ( ! empty( $this->args['user_map'][ $i ] ) ) {
363 $user = get_userdata( (int) $this->args['user_map'][ $i ] );
364 if ( isset( $user->ID ) ) {
365 if ( $old_id ) {
366 $this->processed_authors[ $old_id ] = $user->ID;
367 }
368 $this->author_mapping[ $santized_old_login ] = $user->ID;
369 }
370 } elseif ( $create_users ) {
371 $user_id = 0;
372 if ( ! empty( $this->args['user_new'][ $i ] ) ) {
373 $user_id = wp_create_user( $this->args['user_new'][ $i ], wp_generate_password() );
374 } elseif ( '1.0' !== $this->version ) {
375 $user_data = [
376 'user_login' => $old_login,
377 'user_pass' => wp_generate_password(),
378 'user_email' => isset( $this->authors[ $old_login ]['author_email'] ) ? $this->authors[ $old_login ]['author_email'] : '',
379 'display_name' => $this->authors[ $old_login ]['author_display_name'],
380 'first_name' => isset( $this->authors[ $old_login ]['author_first_name'] ) ? $this->authors[ $old_login ]['author_first_name'] : '',
381 'last_name' => isset( $this->authors[ $old_login ]['author_last_name'] ) ? $this->authors[ $old_login ]['author_last_name'] : '',
382 ];
383 $user_id = wp_insert_user( $user_data );
384 }
385
386 if ( ! is_wp_error( $user_id ) ) {
387 if ( $old_id ) {
388 $this->processed_authors[ $old_id ] = $user_id;
389 }
390 $this->author_mapping[ $santized_old_login ] = $user_id;
391 } else {
392 /* translators: %s: Author display name. */
393 $error = sprintf( esc_html__( 'Failed to create new user for %s. Their posts will be attributed to the current user.', 'elementor' ), $this->authors[ $old_login ]['author_display_name'] );
394
395 if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) {
396 $error .= PHP_EOL . $user_id->get_error_message();
397 }
398
399 $this->output['errors'][] = $error;
400 }
401 }
402
403 // Failsafe: if the user_id was invalid, default to the current user.
404 if ( ! isset( $this->author_mapping[ $santized_old_login ] ) ) {
405 if ( $old_id ) {
406 $this->processed_authors[ $old_id ] = (int) get_current_user_id();
407 }
408 $this->author_mapping[ $santized_old_login ] = (int) get_current_user_id();
409 }
410 }
411 }
412
413 /**
414 * Create new terms based on import information
415 *
416 * Doesn't create a term its slug already exists
417 *
418 * @return array|array[] the ids of succeed/failed imported terms.
419 */
420 private function process_terms(): array {
421 $result = [
422 'succeed' => [],
423 'failed' => [],
424 ];
425
426 $this->terms = apply_filters( 'wp_import_terms', $this->terms );
427 if ( empty( $this->terms ) ) {
428 return $result;
429 }
430 foreach ( $this->terms as $term ) {
431
432 // if the term already exists in the correct taxonomy leave it alone
433 $term_id = term_exists( $term['slug'], $term['term_taxonomy'] );
434 if ( $term_id ) {
435 if ( is_array( $term_id ) ) {
436 $term_id = $term_id['term_id'];
437 }
438
439 if ( isset( $term['term_id'] ) ) {
440 if ( 'nav_menu' === $term['term_taxonomy'] ) {
441 // BC - support old kits that the menu terms are part of the 'nav_menu_item' post type
442 // and not part of the taxonomies.
443 if ( ! empty( $this->processed_taxonomies[ $term['term_taxonomy'] ] ) ) {
444 foreach ( $this->processed_taxonomies[ $term['term_taxonomy'] ] as $processed_term ) {
445 $old_slug = $processed_term['old_slug'];
446 $new_slug = $processed_term['new_slug'];
447
448 $this->mapped_terms_slug[ $old_slug ] = $new_slug;
449 $result['succeed'][ $old_slug ] = $new_slug;
450 }
451 continue;
452 } else {
453 $term = $this->handle_duplicated_nav_menu_term( $term );
454 }
455 } else {
456 $this->processed_terms[ (int) $term['term_id'] ] = (int) $term_id;
457 $result['succeed'][ (int) $term['term_id'] ] = (int) $term_id;
458 continue;
459 }
460 }
461 }
462
463 if ( empty( $term['term_parent'] ) ) {
464 $parent = 0;
465 } else {
466 $parent = term_exists( $term['term_parent'], $term['term_taxonomy'] );
467 if ( is_array( $parent ) ) {
468 $parent = $parent['term_id'];
469 }
470 }
471
472 $description = $term['term_description'] ?? '';
473 $args = [
474 'slug' => $term['slug'],
475 'description' => wp_slash( $description ),
476 'parent' => (int) $parent,
477 ];
478
479 $id = wp_insert_term( wp_slash( $term['term_name'] ), $term['term_taxonomy'], $args );
480 if ( ! is_wp_error( $id ) ) {
481 if ( isset( $term['term_id'] ) ) {
482 $this->processed_terms[ (int) $term['term_id'] ] = $id['term_id'];
483 $result['succeed'][ (int) $term['term_id'] ] = $id['term_id'];
484
485 $this->update_term_meta( $id['term_id'] );
486 }
487 } else {
488 /* translators: 1: Term taxonomy, 2: Term name. */
489 $error = sprintf( esc_html__( 'Failed to import %1$s %2$s', 'elementor' ), $term['term_taxonomy'], $term['term_name'] );
490
491 if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) {
492 $error .= PHP_EOL . $id->get_error_message();
493 }
494
495 $result['failed'][] = $id;
496 $this->output['errors'][] = $error;
497 continue;
498 }
499
500 $this->process_termmeta( $term, $id['term_id'] );
501
502 do_action( 'templately_import.process_term', $term, $result, $this );
503 }
504
505 unset( $this->terms );
506
507 return $result;
508 }
509
510 /**
511 * Add metadata to imported term.
512 *
513 * @param array $term Term data from WXR import.
514 * @param int $term_id ID of the newly created term.
515 */
516 private function process_termmeta( $term, $term_id ) {
517 if ( ! function_exists( 'add_term_meta' ) ) {
518 return;
519 }
520
521 if ( ! isset( $term['termmeta'] ) ) {
522 $term['termmeta'] = [];
523 }
524
525 /**
526 * Filters the metadata attached to an imported term.
527 *
528 * @param array $termmeta Array of term meta.
529 * @param int $term_id ID of the newly created term.
530 * @param array $term Term data from the WXR import.
531 */
532 $term['termmeta'] = apply_filters( 'wp_import_term_meta', $term['termmeta'], $term_id, $term );
533
534 if ( empty( $term['termmeta'] ) ) {
535 return;
536 }
537
538 foreach ( $term['termmeta'] as $meta ) {
539 /**
540 * Filters the meta key for an imported piece of term meta.
541 *
542 * @param string $meta_key Meta key.
543 * @param int $term_id ID of the newly created term.
544 * @param array $term Term data from the WXR import.
545 */
546 $key = apply_filters( 'import_term_meta_key', $meta['key'], $term_id, $term );
547 if ( ! $key ) {
548 continue;
549 }
550
551 // Export gets meta straight from the DB so could have a serialized string
552 $value = maybe_unserialize( $meta['value'] );
553
554 add_term_meta( $term_id, wp_slash( $key ), wp_slash_strings_only( $value ) );
555
556 /**
557 * Fires after term meta is imported.
558 *
559 * @param int $term_id ID of the newly created term.
560 * @param string $key Meta key.
561 * @param mixed $value Meta value.
562 */
563 do_action( 'import_term_meta', $term_id, $key, $value );
564 }
565 }
566
567 /**
568 * Create new posts based on import information
569 *
570 * Posts marked as having a parent which doesn't exist will become top level items.
571 * Doesn't create a new post if: the post type doesn't exist, the given post ID
572 * is already noted as imported or a post with the same title and date already exists.
573 * Note that new/updated terms, comments and meta are imported for the last of the above.
574 *
575 * @return array the ids of succeed/failed imported posts.
576 */
577 private function process_posts(): array {
578 $result = [
579 'succeed' => [],
580 'failed' => [],
581 ];
582
583 $this->posts = apply_filters( 'wp_import_posts', $this->posts );
584
585 foreach ( $this->posts as $post ) {
586 $post = apply_filters( 'wp_import_post_data_raw', $post );
587
588 if ( ! post_type_exists( $post['post_type'] ) ) {
589 /* translators: 1: Post title, 2: Post type. */
590 $this->output['errors'][] = sprintf( esc_html__( 'Failed to import %1$s: Invalid post type %2$s', 'elementor' ), $post['post_title'], $post['post_type'] );
591 do_action( 'wp_import_post_exists', $post );
592 continue;
593 }
594
595 if ( isset( $this->processed_posts[ $post['post_id'] ] ) && ! empty( $post['post_id'] ) ) {
596 continue;
597 }
598
599 if ( 'auto-draft' === $post['status'] ) {
600 continue;
601 }
602
603 if ( 'nav_menu_item' === $post['post_type'] ) {
604 $result['succeed'] += $this->process_menu_item( $post );
605 continue;
606 }
607
608 if ( 'wp_navigation' === $post['post_type'] ) {
609 $processed = $this->process_navigation( $post );
610 if ( ! $processed ) {
611 continue;
612 }
613 }
614
615 $post_type_object = get_post_type_object( $post['post_type'] );
616
617 $post_parent = (int) $post['post_parent'];
618 if ( $post_parent ) {
619 // if we already know the parent, map it to the new local ID.
620 if ( isset( $this->processed_posts[ $post_parent ] ) ) {
621 $post_parent = $this->processed_posts[ $post_parent ];
622 // otherwise record the parent for later.
623 } else {
624 $this->post_orphans[ (int) $post['post_id'] ] = $post_parent;
625 $post_parent = 0;
626 }
627 }
628
629 // Map the post author.
630 $author = sanitize_user( $post['post_author'], true );
631 if ( isset( $this->author_mapping[ $author ] ) ) {
632 $author = $this->author_mapping[ $author ];
633 } else {
634 $author = (int) get_current_user_id();
635 }
636
637 $postdata = [
638 'post_author' => $author,
639 'post_content' => $post['post_content'],
640 'post_excerpt' => $post['post_excerpt'],
641 'post_title' => $post['post_title'],
642 'post_status' => $post['status'],
643 'post_name' => $post['post_name'],
644 'comment_status' => $post['comment_status'],
645 'ping_status' => $post['ping_status'],
646 'guid' => $post['guid'],
647 'post_parent' => $post_parent,
648 'menu_order' => $post['menu_order'],
649 'post_type' => $post['post_type'],
650 'post_password' => $post['post_password'],
651 ];
652
653 $original_post_id = $post['post_id'];
654 $postdata = apply_filters( 'wp_import_post_data_processed', $postdata, $post );
655
656 $postdata = wp_slash( $postdata );
657
658 if ( 'attachment' === $postdata['post_type'] ) {
659 $remote_url = ! empty( $post['attachment_url'] ) ? $post['attachment_url'] : $post['guid'];
660
661 // try to use _wp_attached file for upload folder placement to ensure the same location as the export site
662 // e.g. location is 2003/05/image.jpg but the attachment post_date is 2010/09, see media_handle_upload()
663 $postdata['upload_date'] = $post['post_date'];
664 if ( isset( $post['postmeta'] ) ) {
665 foreach ( $post['postmeta'] as $meta ) {
666 if ( '_wp_attached_file' === $meta['key'] ) {
667 if ( preg_match( '%^[0-9]{4}/[0-9]{2}%', $meta['value'], $matches ) ) {
668 $postdata['upload_date'] = $matches[0];
669 }
670 break;
671 }
672 }
673 }
674
675 $post_id = $this->process_attachment( $postdata, $remote_url );
676 $comment_post_id = $post_id;
677 } else {
678 $post_id = wp_insert_post( $postdata, true );
679
680 $this->update_post_meta( $post_id );
681
682 $comment_post_id = $post_id;
683 do_action( 'wp_import_insert_post', $post_id, $original_post_id, $postdata, $post );
684 }
685
686 if ( is_wp_error( $post_id ) ) {
687 /* translators: 1: Post type singular label, 2: Post title. */
688 $error = sprintf( __( 'Failed to import %1$s %2$s', 'elementor' ), $post_type_object->labels->singular_name, $post['post_title'] );
689
690 if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) {
691 $error .= PHP_EOL . $post_id->get_error_message();
692 }
693
694 $result['failed'][] = $original_post_id;
695
696 $this->output['errors'][] = $error;
697
698 continue;
699 }
700
701 $result['succeed'][ $original_post_id ] = $post_id;
702
703 if ( 1 === $post['is_sticky'] ) {
704 stick_post( $post_id );
705 }
706
707 if ( $this->page_on_front === $original_post_id ) {
708 Utils::update_option( 'page_on_front', $post_id );
709 }
710
711 // Map pre-import ID to local ID.
712 $this->processed_posts[ (int) $post['post_id'] ] = (int) $post_id;
713
714 if ( ! isset( $post['terms'] ) ) {
715 $post['terms'] = [];
716 }
717
718 $post['terms'] = apply_filters( 'wp_import_post_terms', $post['terms'], $post_id, $post );
719
720 // add categories, tags and other terms
721 if ( ! empty( $post['terms'] ) ) {
722 $terms_to_set = [];
723 foreach ( $post['terms'] as $term ) {
724 // back compat with WXR 1.0 map 'tag' to 'post_tag'
725 $taxonomy = ( 'tag' === $term['domain'] ) ? 'post_tag' : $term['domain'];
726 $term_exists = term_exists( $term['slug'], $taxonomy );
727 $term_id = is_array( $term_exists ) ? $term_exists['term_id'] : $term_exists;
728 if ( ! $term_id ) {
729 $t = wp_insert_term( $term['name'], $taxonomy, [ 'slug' => $term['slug'] ] );
730 if ( ! is_wp_error( $t ) ) {
731 $term_id = $t['term_id'];
732
733 $this->update_term_meta( $term_id );
734
735 do_action( 'wp_import_insert_term', $t, $term, $post_id, $post );
736 } else {
737 /* translators: 1: Taxonomy name, 2: Term name. */
738 $error = sprintf( esc_html__( 'Failed to import %1$s %2$s', 'elementor' ), $taxonomy, $term['name'] );
739
740 if ( defined( 'IMPORT_DEBUG' ) && IMPORT_DEBUG ) {
741 $error .= PHP_EOL . $t->get_error_message();
742 }
743
744 $this->output['errors'][] = $error;
745
746 do_action( 'wp_import_insert_term_failed', $t, $term, $post_id, $post );
747 continue;
748 }
749 }
750 $terms_to_set[ $taxonomy ][] = (int) $term_id;
751 }
752
753 foreach ( $terms_to_set as $tax => $ids ) {
754 $tt_ids = wp_set_post_terms( $post_id, $ids, $tax );
755 do_action( 'wp_import_set_post_terms', $tt_ids, $ids, $tax, $post_id, $post );
756 }
757 unset( $post['terms'], $terms_to_set );
758 }
759
760 if ( ! isset( $post['comments'] ) ) {
761 $post['comments'] = [];
762 }
763
764 $post['comments'] = apply_filters( 'wp_import_post_comments', $post['comments'], $post_id, $post );
765
766 // Add/update comments.
767 if ( ! empty( $post['comments'] ) ) {
768 $num_comments = 0;
769 $inserted_comments = [];
770 foreach ( $post['comments'] as $comment ) {
771 $comment_id = $comment['comment_id'];
772 $newcomments[ $comment_id ]['comment_post_ID'] = $comment_post_id;
773 $newcomments[ $comment_id ]['comment_author'] = $comment['comment_author'];
774 $newcomments[ $comment_id ]['comment_author_email'] = $comment['comment_author_email'];
775 $newcomments[ $comment_id ]['comment_author_IP'] = $comment['comment_author_IP'];
776 $newcomments[ $comment_id ]['comment_author_url'] = $comment['comment_author_url'];
777 $newcomments[ $comment_id ]['comment_date'] = $comment['comment_date'];
778 $newcomments[ $comment_id ]['comment_date_gmt'] = $comment['comment_date_gmt'];
779 $newcomments[ $comment_id ]['comment_content'] = $comment['comment_content'];
780 $newcomments[ $comment_id ]['comment_approved'] = $comment['comment_approved'];
781 $newcomments[ $comment_id ]['comment_type'] = $comment['comment_type'];
782 $newcomments[ $comment_id ]['comment_parent'] = $comment['comment_parent'];
783 $newcomments[ $comment_id ]['commentmeta'] = isset( $comment['commentmeta'] ) ? $comment['commentmeta'] : [];
784 if ( isset( $this->processed_authors[ $comment['comment_user_id'] ] ) ) {
785 $newcomments[ $comment_id ]['user_id'] = $this->processed_authors[ $comment['comment_user_id'] ];
786 }
787 }
788
789 ksort( $newcomments );
790
791 foreach ( $newcomments as $key => $comment ) {
792 if ( isset( $inserted_comments[ $comment['comment_parent'] ] ) ) {
793 $comment['comment_parent'] = $inserted_comments[ $comment['comment_parent'] ];
794 }
795
796 $comment_data = wp_slash( $comment );
797 unset( $comment_data['commentmeta'] ); // Handled separately, wp_insert_comment() also expects `comment_meta`.
798 $comment_data = wp_filter_comment( $comment_data );
799
800 $inserted_comments[ $key ] = wp_insert_comment( $comment_data );
801
802 do_action( 'wp_import_insert_comment', $inserted_comments[ $key ], $comment, $comment_post_id, $post );
803
804 foreach ( $comment['commentmeta'] as $meta ) {
805 $value = maybe_unserialize( $meta['value'] );
806
807 add_comment_meta( $inserted_comments[ $key ], wp_slash( $meta['key'] ), wp_slash_strings_only( $value ) );
808 }
809
810 $num_comments++;
811 }
812 unset( $newcomments, $inserted_comments, $post['comments'] );
813 }
814
815 if ( ! isset( $post['postmeta'] ) ) {
816 $post['postmeta'] = [];
817 }
818
819 $post['postmeta'] = apply_filters( 'wp_import_post_meta', $post['postmeta'], $post_id, $post );
820
821 // Add/update post meta.
822 if ( ! empty( $post['postmeta'] ) ) {
823 foreach ( $post['postmeta'] as $meta ) {
824 $key = apply_filters( 'import_post_meta_key', $meta['key'], $post_id, $post );
825 $value = false;
826
827 if ( '_edit_last' === $key ) {
828 if ( isset( $this->processed_authors[ (int) $meta['value'] ] ) ) {
829 $value = $this->processed_authors[ (int) $meta['value'] ];
830 } else {
831 $key = false;
832 }
833 }
834
835 if ( $key ) {
836 // Export gets meta straight from the DB so could have a serialized string.
837 if ( ! $value ) {
838 $value = maybe_unserialize( $meta['value'] );
839 }
840
841 add_post_meta( $post_id, wp_slash( $key ), wp_slash_strings_only( $value ) );
842
843 do_action( 'import_post_meta', $post_id, $key, $value );
844
845 // If the post has a featured image, take note of this in case of remap.
846 if ( '_thumbnail_id' === $key ) {
847 $this->featured_images[ $post_id ] = (int) $value;
848 }
849 }
850 }
851 }
852
853 do_action( 'templately_import.process_post', $post, $result, $this );
854 }
855
856 unset( $this->posts );
857
858 return $result;
859 }
860
861 /**
862 * Attempt to create a new menu item from import data
863 *
864 * Fails for draft, orphaned menu items and those without an associated nav_menu
865 * or an invalid nav_menu term. If the post type or term object which the menu item
866 * represents doesn't exist then the menu item will not be imported (waits until the
867 * end of the import to retry again before discarding).
868 *
869 * @param array $item Menu item details from WXR file
870 */
871 private function process_menu_item( $item ) {
872 $result = [];
873
874 // Skip draft, orphaned menu items.
875 if ( 'draft' === $item['status'] ) {
876 return;
877 }
878
879 $menu_slug = false;
880 if ( isset( $item['terms'] ) ) {
881 // Loop through terms, assume first nav_menu term is correct menu.
882 foreach ( $item['terms'] as $term ) {
883 if ( 'nav_menu' === $term['domain'] ) {
884 $menu_slug = $term['slug'];
885 break;
886 }
887 }
888 }
889
890 // No nav_menu term associated with this menu item.
891 if ( ! $menu_slug ) {
892 $this->output['errors'][] = esc_html__( 'Menu item skipped due to missing menu slug', 'elementor' );
893
894 return $result;
895 }
896
897 // If menu was already exists, refer the items to the duplicated menu created.
898 if ( array_key_exists( $menu_slug, $this->mapped_terms_slug ) ) {
899 $menu_slug = $this->mapped_terms_slug[ $menu_slug ];
900 }
901
902 $menu_id = term_exists( $menu_slug, 'nav_menu' );
903 if ( ! $menu_id ) {
904 /* translators: %s: Menu slug. */
905 $this->output['errors'][] = sprintf( esc_html__( 'Menu item skipped due to invalid menu slug: %s', 'elementor' ), $menu_slug );
906
907 return $result;
908 } else {
909 $menu_id = is_array( $menu_id ) ? $menu_id['term_id'] : $menu_id;
910 }
911
912 $post_meta_key_value = [];
913 foreach ( $item['postmeta'] as $meta ) {
914 $post_meta_key_value[ $meta['key'] ] = $meta['value'];
915 }
916
917 $_menu_item_type = $post_meta_key_value['_menu_item_type'];
918 $_menu_item_url = $post_meta_key_value['_menu_item_url'];
919
920 // Skip menu items 'taxonomy' type, when the taxonomy is not exits.
921 if ( 'taxonomy' === $_menu_item_type && ! taxonomy_exists( $post_meta_key_value['_menu_item_object'] ) ) {
922 return $result;
923 }
924
925 // Skip menu items 'post_type' type, when the post type is not exits.
926 if ( 'post_type' === $_menu_item_type && ! post_type_exists( $post_meta_key_value['_menu_item_object'] ) ) {
927 return $result;
928 }
929
930 $_menu_item_object_id = $post_meta_key_value['_menu_item_object_id'];
931 if ( 'taxonomy' === $_menu_item_type && isset( $this->processed_terms[ (int) $_menu_item_object_id ] ) ) {
932 $_menu_item_object_id = $this->processed_terms[ (int) $_menu_item_object_id ];
933 } elseif ( 'post_type' === $_menu_item_type && isset( $this->processed_posts[ (int) $_menu_item_object_id ] ) ) {
934 $_menu_item_object_id = $this->processed_posts[ (int) $_menu_item_object_id ];
935 } elseif ( 'custom' === $_menu_item_type ) {
936 // FIXME: Later on you need to check if there is custom menu link related fixes any.
937 $_menu_item_url = URL::migrate( $_menu_item_url, $this->base_blog_url );
938 if ( str_starts_with( $_menu_item_url, $this->base_blog_url ) ) {
939 $_menu_item_url = '#';
940 }
941 } else {
942 return $result;
943 }
944
945 $_menu_item_menu_item_parent = $post_meta_key_value['_menu_item_menu_item_parent'];
946 if ( isset( $this->processed_menu_items[ (int) $_menu_item_menu_item_parent ] ) ) {
947 $_menu_item_menu_item_parent = $this->processed_menu_items[ (int) $_menu_item_menu_item_parent ];
948 } elseif ( $_menu_item_menu_item_parent ) {
949 $this->menu_item_orphans[ (int) $item['post_id'] ] = (int) $_menu_item_menu_item_parent;
950 $_menu_item_menu_item_parent = 0;
951 }
952
953 // wp_update_nav_menu_item expects CSS classes as a space separated string
954 $_menu_item_classes = maybe_unserialize( $post_meta_key_value['_menu_item_classes'] );
955 if ( is_array( $_menu_item_classes ) ) {
956 $_menu_item_classes = implode( ' ', $_menu_item_classes );
957 }
958
959 $args = [
960 'menu-item-object-id' => $_menu_item_object_id,
961 'menu-item-object' => $post_meta_key_value['_menu_item_object'],
962 'menu-item-parent-id' => $_menu_item_menu_item_parent,
963 'menu-item-position' => (int) $item['menu_order'],
964 'menu-item-type' => $_menu_item_type,
965 'menu-item-title' => $item['post_title'],
966 'menu-item-url' => $_menu_item_url,
967 'menu-item-description' => $item['post_content'],
968 'menu-item-attr-title' => $item['post_excerpt'],
969 'menu-item-target' => $post_meta_key_value['_menu_item_target'],
970 'menu-item-classes' => $_menu_item_classes,
971 'menu-item-xfn' => $post_meta_key_value['_menu_item_xfn'],
972 'menu-item-status' => $item['status'],
973 ];
974
975 $id = wp_update_nav_menu_item( $menu_id, 0, $args );
976 if ( $id && ! is_wp_error( $id ) ) {
977 $this->processed_menu_items[ (int) $item['post_id'] ] = (int) $id;
978 $result[ $item['post_id'] ] = $id;
979
980 $this->update_post_meta( $id );
981 }
982
983 return $result;
984 }
985
986 private function process_navigation( &$item ): bool {
987 if ( 'draft' === $item['status'] ) {
988 return false;
989 }
990
991 $content = parse_blocks( $item['post_content'] );
992
993 $parsed_blocks = [];
994 foreach ( $content as $block ) {
995 if ( empty( $block['blockName'] ) ) {
996 continue;
997 }
998
999 $this->prepare_block( $block );
1000 $parsed_blocks[] = $block;
1001 }
1002
1003 $item['post_content'] = serialize_blocks( $parsed_blocks );
1004
1005 return true;
1006 }
1007
1008 private function prepare_block( &$block ) {
1009 if ( $block['blockName'] == 'core/navigation-link' || $block['blockName'] == 'core/navigation-submenu' ) {
1010 $attrs = &$block['attrs'];
1011 switch ( $attrs['kind'] ) {
1012 case 'post-type':
1013 if ( isset( $this->processed_posts[ (int) $attrs['id'] ] ) ) {
1014 $attrs['id'] = $this->processed_posts[ (int) $attrs['id'] ];
1015 $attrs['url'] = get_permalink( $attrs['id'] );
1016 }
1017 break;
1018 case 'taxonomy':
1019 if ( isset( $this->processed_terms[ (int) $attrs['id'] ] ) ) {
1020 $attrs['id'] = $this->processed_terms[ (int) $attrs['id'] ];
1021 $attrs['url'] = get_term_link( $attrs['id'], $attrs['type'] );
1022 }
1023 break;
1024 }
1025 if ( ! empty( $block['innerBlocks'] ) ) {
1026 foreach ( $block['innerBlocks'] as &$b ) {
1027 $this->prepare_block( $b );
1028 }
1029 }
1030 }
1031 }
1032
1033 /**
1034 * If fetching attachments is enabled then attempt to create a new attachment
1035 *
1036 * @param array $post Attachment post details from WXR
1037 * @param string $url URL to fetch attachment from
1038 *
1039 * @return int|WP_Error Post ID on success, WP_Error otherwise
1040 */
1041 private function process_attachment( $post, $url ) {
1042 if ( ! $this->fetch_attachments ) {
1043 return new WP_Error( 'attachment_processing_error', esc_html__( 'Fetching attachments is not enabled', 'elementor' ) );
1044 }
1045
1046 // if the URL is absolute, but does not contain address, then upload it assuming base_site_url.
1047 if ( preg_match( '|^/[\w\W]+$|', $url ) ) {
1048 $url = rtrim( $this->base_url, '/' ) . $url;
1049 }
1050
1051 $upload = $this->fetch_remote_file( $url, $post );
1052 if ( is_wp_error( $upload ) ) {
1053 return $upload;
1054 }
1055
1056 $info = wp_check_filetype( $upload['file'] );
1057 if ( $info ) {
1058 $post['post_mime_type'] = $info['type'];
1059 } else {
1060 return new WP_Error( 'attachment_processing_error', esc_html__( 'Invalid file type', 'elementor' ) );
1061 }
1062
1063 $post['guid'] = $upload['url'];
1064
1065 // As per wp-admin/includes/upload.php.
1066 $post_id = wp_insert_attachment( $post, $upload['file'] );
1067
1068 $this->update_post_meta( $post_id );
1069
1070 wp_update_attachment_metadata( $post_id, wp_generate_attachment_metadata( $post_id, $upload['file'] ) );
1071
1072 // Remap resized image URLs, works by stripping the extension and remapping the URL stub.
1073 if ( preg_match( '!^image/!', $info['type'] ) ) {
1074 $parts = pathinfo( $url );
1075 $name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2
1076
1077 $parts_new = pathinfo( $upload['url'] );
1078 $name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" );
1079
1080 $this->url_remap[ $parts['dirname'] . '/' . $name ] = $parts_new['dirname'] . '/' . $name_new;
1081 }
1082
1083 return $post_id;
1084 }
1085
1086 /**
1087 * Attempt to download a remote file attachment
1088 *
1089 * @param string $url URL of item to fetch
1090 * @param array $post Attachment details
1091 *
1092 * @return array|WP_Error Local file location details on success, WP_Error otherwise
1093 */
1094 private function fetch_remote_file( $url, $post ) {
1095 // Extract the file name from the URL.
1096 $file_name = basename( parse_url( $url, PHP_URL_PATH ) );
1097
1098 if ( ! $file_name ) {
1099 $file_name = md5( $url );
1100 }
1101
1102 $tmp_file_name = wp_tempnam( $file_name );
1103 if ( ! $tmp_file_name ) {
1104 return new WP_Error( 'import_no_file', esc_html__( 'Could not create temporary file.', 'elementor' ) );
1105 }
1106
1107 // Fetch the remote URL and write it to the placeholder file.
1108 $remote_response = wp_safe_remote_get( $url, [
1109 'timeout' => 300,
1110 'stream' => true,
1111 'filename' => $tmp_file_name,
1112 'headers' => [
1113 'Accept-Encoding' => 'identity',
1114 ]
1115 ] );
1116
1117 if ( is_wp_error( $remote_response ) ) {
1118 @unlink( $tmp_file_name );
1119
1120 return new WP_Error( 'import_file_error', sprintf( /* translators: 1: WordPress error message, 2: WordPress error code. */ esc_html__( 'Request failed due to an error: %1$s (%2$s)', 'elementor' ), esc_html( $remote_response->get_error_message() ), esc_html( $remote_response->get_error_code() ) ) );
1121 }
1122
1123 $remote_response_code = (int) wp_remote_retrieve_response_code( $remote_response );
1124
1125 // Make sure the fetch was successful.
1126 if ( 200 !== $remote_response_code ) {
1127 @unlink( $tmp_file_name );
1128
1129 return new WP_Error( 'import_file_error', sprintf( /* translators: 1: HTTP error message, 2: HTTP error code. */ esc_html__( 'Remote server returned the following unexpected result: %1$s (%2$s)', 'elementor' ), get_status_header_desc( $remote_response_code ), esc_html( $remote_response_code ) ) );
1130 }
1131
1132 $headers = wp_remote_retrieve_headers( $remote_response );
1133
1134 // Request failed.
1135 if ( ! $headers ) {
1136 @unlink( $tmp_file_name );
1137
1138 return new WP_Error( 'import_file_error', esc_html__( 'Remote server did not respond', 'elementor' ) );
1139 }
1140
1141 $filesize = (int) filesize( $tmp_file_name );
1142
1143 if ( 0 === $filesize ) {
1144 @unlink( $tmp_file_name );
1145
1146 return new WP_Error( 'import_file_error', esc_html__( 'Zero size file downloaded', 'elementor' ) );
1147 }
1148
1149 if ( ! isset( $headers['content-encoding'] ) && isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) {
1150 @unlink( $tmp_file_name );
1151
1152 return new WP_Error( 'import_file_error', esc_html__( 'Downloaded file has incorrect size', 'elementor' ) );
1153 }
1154
1155 $max_size = (int) apply_filters( 'import_attachment_size_limit', self::DEFAULT_IMPORT_ATTACHMENT_SIZE_LIMIT );
1156 if ( ! empty( $max_size ) && $filesize > $max_size ) {
1157 @unlink( $tmp_file_name );
1158
1159 /* translators: %s: Max file size. */
1160
1161 return new WP_Error( 'import_file_error', sprintf( esc_html__( 'Remote file is too large, limit is %s', 'elementor' ), size_format( $max_size ) ) );
1162 }
1163
1164 // Override file name with Content-Disposition header value.
1165 if ( ! empty( $headers['content-disposition'] ) ) {
1166 $file_name_from_disposition = self::get_filename_from_disposition( (array) $headers['content-disposition'] );
1167 if ( $file_name_from_disposition ) {
1168 $file_name = $file_name_from_disposition;
1169 }
1170 }
1171
1172 // Set file extension if missing.
1173 $file_ext = pathinfo( $file_name, PATHINFO_EXTENSION );
1174 if ( ! $file_ext && ! empty( $headers['content-type'] ) ) {
1175 $extension = self::get_file_extension_by_mime_type( $headers['content-type'] );
1176 if ( $extension ) {
1177 $file_name = "{$file_name}.{$extension}";
1178 }
1179 }
1180
1181 // Handle the upload like _wp_handle_upload() does.
1182 $wp_filetype = wp_check_filetype_and_ext( $tmp_file_name, $file_name );
1183 $ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
1184 $type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
1185 $proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
1186
1187 // Check to see if wp_check_filetype_and_ext() determined the filename was incorrect.
1188 if ( $proper_filename ) {
1189 $file_name = $proper_filename;
1190 }
1191
1192 if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
1193 return new WP_Error( 'import_file_error', esc_html__( 'Sorry, this file type is not permitted for security reasons.', 'elementor' ) );
1194 }
1195
1196 $uploads = wp_upload_dir( $post['upload_date'] );
1197 if ( ! ( $uploads && false === $uploads['error'] ) ) {
1198 return new WP_Error( 'upload_dir_error', $uploads['error'] );
1199 }
1200
1201 // Move the file to the uploads dir.
1202 $file_name = wp_unique_filename( $uploads['path'], $file_name );
1203 $new_file = $uploads['path'] . "/$file_name";
1204 $move_new_file = copy( $tmp_file_name, $new_file );
1205
1206 if ( ! $move_new_file ) {
1207 @unlink( $tmp_file_name );
1208
1209 return new WP_Error( 'import_file_error', esc_html__( 'The uploaded file could not be moved', 'elementor' ) );
1210 }
1211
1212 // Set correct file permissions.
1213 $stat = stat( dirname( $new_file ) );
1214 $perms = $stat['mode'] & 0000666;
1215 chmod( $new_file, $perms );
1216
1217 $upload = [
1218 'file' => $new_file,
1219 'url' => $uploads['url'] . "/$file_name",
1220 'type' => $wp_filetype['type'],
1221 'error' => false,
1222 ];
1223
1224 // Keep track of the old and new urls so we can substitute them later.
1225 $this->url_remap[ $url ] = $upload['url'];
1226 $this->url_remap[ $post['guid'] ] = $upload['url']; // r13735, really needed?
1227 // Keep track of the destination if the remote url is redirected somewhere else.
1228 if ( isset( $headers['x-final-location'] ) && $headers['x-final-location'] !== $url ) {
1229 $this->url_remap[ $headers['x-final-location'] ] = $upload['url'];
1230 }
1231
1232 return $upload;
1233 }
1234
1235 /**
1236 * Attempt to associate posts and menu items with previously missing parents
1237 *
1238 * An imported post's parent may not have been imported when it was first created
1239 * so try again. Similarly for child menu items and menu items which were missing
1240 * the object (e.g. post) they represent in the menu
1241 */
1242 private function backfill_parents() {
1243 global $wpdb;
1244
1245 // Find parents for post orphans.
1246 foreach ( $this->post_orphans as $child_id => $parent_id ) {
1247 $local_child_id = false;
1248 $local_parent_id = false;
1249
1250 if ( isset( $this->processed_posts[ $child_id ] ) ) {
1251 $local_child_id = $this->processed_posts[ $child_id ];
1252 }
1253 if ( isset( $this->processed_posts[ $parent_id ] ) ) {
1254 $local_parent_id = $this->processed_posts[ $parent_id ];
1255 }
1256
1257 if ( $local_child_id && $local_parent_id ) {
1258 $wpdb->update( $wpdb->posts, [ 'post_parent' => $local_parent_id ], [ 'ID' => $local_child_id ], '%d', '%d' );
1259 clean_post_cache( $local_child_id );
1260 }
1261 }
1262
1263 // Find parents for menu item orphans.
1264 foreach ( $this->menu_item_orphans as $child_id => $parent_id ) {
1265 $local_child_id = 0;
1266 $local_parent_id = 0;
1267 if ( isset( $this->processed_menu_items[ $child_id ] ) ) {
1268 $local_child_id = $this->processed_menu_items[ $child_id ];
1269 }
1270 if ( isset( $this->processed_menu_items[ $parent_id ] ) ) {
1271 $local_parent_id = $this->processed_menu_items[ $parent_id ];
1272 }
1273
1274 if ( $local_child_id && $local_parent_id ) {
1275 update_post_meta( $local_child_id, '_menu_item_menu_item_parent', (int) $local_parent_id );
1276 }
1277 }
1278 }
1279
1280 /**
1281 * Use stored mapping information to update old attachment URLs
1282 */
1283 private function backfill_attachment_urls() {
1284 global $wpdb;
1285 // Make sure we do the longest urls first, in case one is a substring of another.
1286 uksort( $this->url_remap, function ( $a, $b ) {
1287 // Return the difference in length between two strings.
1288 return strlen( $b ) - strlen( $a );
1289 } );
1290
1291 foreach ( $this->url_remap as $from_url => $to_url ) {
1292 // Remap urls in post_content.
1293 $processed_posts_placeholders = implode(',', array_fill(0, count($this->processed_posts), '%d'));
1294
1295 // Prepare the query for posts
1296 $query_posts = $wpdb->prepare(
1297 "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s) WHERE ID IN ($processed_posts_placeholders)",
1298 $from_url,
1299 $to_url,
1300 ...$this->processed_posts
1301 );
1302 $wpdb->query($query_posts);
1303
1304 // Prepare the query for postmeta
1305 $query_postmeta = $wpdb->prepare(
1306 "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure' AND post_id IN ($processed_posts_placeholders)",
1307 $from_url,
1308 $to_url,
1309 ...$this->processed_posts
1310 );
1311 $wpdb->query($query_postmeta);
1312 }
1313 }
1314
1315 /**
1316 * Update _thumbnail_id meta to new, imported attachment IDs
1317 */
1318 private function remap_featured_images() {
1319 // Cycle through posts that have a featured image.
1320 foreach ( $this->featured_images as $post_id => $value ) {
1321 if ( isset( $this->processed_posts[ $value ] ) ) {
1322 $new_id = $this->processed_posts[ $value ];
1323 // Only update if there's a difference.
1324 if ( $new_id !== $value ) {
1325 update_post_meta( $post_id, '_thumbnail_id', $new_id );
1326 }
1327 }
1328 }
1329 }
1330
1331 /**
1332 * Parse a WXR file
1333 *
1334 * @param string $file Path to WXR file for parsing
1335 *
1336 * @return array Information gathered from the WXR file
1337 */
1338 private function parse( $file ): array {
1339 $parser = new WXR_Parser();
1340
1341 return $parser->parse( $file );
1342 }
1343
1344 /**
1345 * Decide if the given meta key maps to information we will want to import
1346 *
1347 * @param string $key The meta key to check
1348 *
1349 * @return string|bool The key if we do want to import, false if not
1350 */
1351 private function is_valid_meta_key( $key ) {
1352 // Skip attachment metadata since we'll regenerate it from scratch.
1353 // Skip _edit_lock as not relevant for import
1354 if ( in_array( $key, [ '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ] ) ) {
1355 return false;
1356 }
1357
1358 return $key;
1359 }
1360
1361 /**
1362 * @param $term
1363 *
1364 * @return mixed
1365 */
1366 private function handle_duplicated_nav_menu_term( $term ) {
1367 $duplicate_slug = $term['slug'] . '-duplicate';
1368 $duplicate_name = $term['term_name'] . ' duplicate';
1369
1370 while ( term_exists( $duplicate_slug, 'nav_menu' ) ) {
1371 $duplicate_slug .= '-duplicate';
1372 $duplicate_name .= ' duplicate';
1373 }
1374
1375 $this->mapped_terms_slug[ $term['slug'] ] = $duplicate_slug;
1376
1377 $term['slug'] = $duplicate_slug;
1378 $term['term_name'] = $duplicate_name;
1379
1380 return $term;
1381 }
1382
1383 /**
1384 * Add all term_meta to specified term.
1385 *
1386 * @param $term_id
1387 *
1388 * @return void
1389 */
1390 private function update_term_meta( $term_id ) {
1391 foreach ( $this->terms_meta as $meta_key => $meta_value ) {
1392 update_term_meta( $term_id, $meta_key, $meta_value );
1393 }
1394 }
1395
1396 /**
1397 * Add all post_meta to specified term.
1398 *
1399 * @param $post_id
1400 *
1401 * @return void
1402 */
1403 private function update_post_meta( $post_id ) {
1404 foreach ( $this->posts_meta as $meta_key => $meta_value ) {
1405 update_post_meta( $post_id, $meta_key, $meta_value );
1406 }
1407 }
1408
1409 public function run(): array {
1410 $this->import( $this->requested_file_path );
1411
1412 return $this->output;
1413 }
1414
1415 /**
1416 * @param $file
1417 * @param array $args
1418 */
1419 public function __construct( $file, array $args = [] ) {
1420 parent::__construct();
1421
1422 $this->requested_file_path = $file;
1423 $this->args = $args;
1424
1425 if ( ! empty( $this->args['fetch_attachments'] ) ) {
1426 $this->fetch_attachments = true;
1427 }
1428
1429 if ( isset( $this->args['posts'] ) && is_array( $this->args['posts'] ) ) {
1430 $this->processed_posts = $this->args['posts'];
1431 }
1432
1433 if ( isset( $this->args['terms'] ) && is_array( $this->args['terms'] ) ) {
1434 $this->processed_terms = $this->args['terms'];
1435 }
1436
1437 if ( isset( $this->args['taxonomies'] ) && is_array( $this->args['taxonomies'] ) ) {
1438 $this->processed_taxonomies = $this->args['taxonomies'];
1439 }
1440
1441 if ( ! empty( $this->args['posts_meta'] ) ) {
1442 $this->posts_meta = $this->args['posts_meta'];
1443 }
1444
1445 if ( ! empty( $this->args['terms_meta'] ) ) {
1446 $this->terms_meta = $this->args['terms_meta'];
1447 }
1448 }
1449 }
1450