PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.1.9
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.1.9
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.1.9, at includes/Core/Importer/WPImport.php

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