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

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