PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.4.9
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.4.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
← All changes | includes/Core/Importer/WPImport.php +17 -129 3.7.53.4.9 View file →
@@ -87,18 +87,8 @@
87 87 * WXR attachment ID
88 88 */
89 89 private $id;
90 90
91 - /**
92 - * @var mixed
93 - */
94 - private $json;
95 -
96 - /**
97 - * @var string
98 - */
99 - private $session_id;
100 -
101 91 // Information to import from WXR file.
102 92 private $version;
103 93 private $authors = [];
104 94 public $posts = [];
@@ -119,10 +109,8 @@
119 109 private $menu_item_orphans = [];
120 110 private $mapped_terms_slug = [];
121 111
122 112 private $fetch_attachments = false;
123 - private $attachment_timeout = 300;
124 - private $attachment_retry_count = 3;
125 113 private $featured_images = [];
126 114
127 115 /**
128 116 * @var array[] [meta_key => meta_value] Meta value that should be set for every imported post.
@@ -397,9 +385,9 @@
397 385 return;
398 386 }
399 387
400 388
401 - $processed_templates = $this->get_loop_result([], $this->import_data_key);
389 + $processed_templates = $this->get_progress([], $this->import_data_key);
402 390 if (!empty($processed_templates)) {
403 391 return;
404 392 }
405 393
@@ -459,9 +447,9 @@
459 447 $this->author_mapping[ $santized_old_login ] = (int) get_current_user_id();
460 448 }
461 449 }
462 450
463 - $this->set_loop_result( true, $this->import_data_key );
451 + $this->update_progress( true, null, $this->import_data_key );
464 452 }
465 453
466 454 /**
467 455 * Create new terms based on import information
@@ -475,11 +463,11 @@
475 463 'succeed' => [],
476 464 'failed' => [],
477 465 ];
478 466
479 - $processed_templates = $this->get_loop_result([], "wp_import_terms_" . $this->import_data_key);
467 + $processed_templates = $this->get_progress([], "wp_import_terms_" . $this->import_data_key);
480 468 if (!empty($processed_templates)) {
481 - $result = $this->get_loop_result([], "wp_import_terms_" . $this->import_data_key);
469 + $result = $this->get_result([], "wp_import_terms_" . $this->import_data_key);
482 470 return $result;
483 471 }
484 472
485 473 $this->terms = apply_filters( 'wp_import_terms', $this->terms );
@@ -563,9 +551,9 @@
563 551
564 552 unset( $this->terms );
565 553
566 554 // Add the template to the processed templates and update the session data
567 - $this->set_loop_result( $result, "wp_import_terms_" . $this->import_data_key);
555 + $this->update_progress( true, $result, "wp_import_terms_" . $this->import_data_key);
568 556 return $result;
569 557 }
570 558
571 559 /**
@@ -1144,30 +1132,14 @@
1144 1132
1145 1133 if($saved_image = $this->get_saved_image($url)){
1146 1134 // $this->url_remap[ $url ] = wp_get_attachment_url( $saved_image );
1147 1135 // $this->url_remap[ $this->remove_extension($url) ] = $this->remove_extension(wp_get_attachment_url( $saved_image ));
1148 - $upload_url = set_url_scheme( wp_get_attachment_url( $saved_image ) );
1136 + $upload_url = wp_get_attachment_url( $saved_image );
1149 1137 $this->set_url_map($url, $upload_url, true);
1150 1138
1151 - // AI image replacement: the imported content still carries the ORIGINAL
1152 - // demo URL, so it must be remapped onto the replacement image here too.
1153 - // The fresh-upload branch below does this, but when the replacement
1154 - // image was already imported (a re-import, or the same stock photo
1155 - // reused) we land here instead — without the map the demo URL survives
1156 - // finalize and the pack's original images are silently kept.
1157 - if(!empty($post['original_attachment_url'])){
1158 - $this->set_url_map($post['original_attachment_url'], $upload_url, true);
1159 -
1160 - $original_hash = sha1( $post['original_attachment_url'] );
1161 -
1162 - // A stale hash from a previous import can still point the demo URL at
1163 - // the old, unreplaced attachment — re-point it at the replacement.
1164 - if( (int) $this->get_saved_image($post['original_attachment_url']) !== (int) $saved_image ){
1165 - $hash_meta_id = add_post_meta( $saved_image, '_elementor_source_image_hash', $original_hash );
1166 - add_post_meta( $saved_image, '_templately_image_hash_meta_id', $hash_meta_id );
1167 - }
1168 -
1169 - self::$_replace_image_ids[ $original_hash ] = (int) $saved_image;
1139 + if(!empty($post['original_attachment_url']) && !$this->get_saved_image($post['original_attachment_url'])){
1140 + add_post_meta( $saved_image, '_elementor_source_image_hash', sha1( $post['original_attachment_url'] ) );
1141 + self::$_replace_image_ids[ sha1( $post['original_attachment_url'] ) ] = $post_id;
1170 1142 }
1171 1143
1172 1144 $full_size_path = get_attached_file($saved_image);
1173 1145 $metadata = wp_get_attachment_metadata($saved_image);
@@ -1194,22 +1166,9 @@
1194 1166 return new WP_Error( 'upload_dir_error', $upload_dir['error'] );
1195 1167 }
1196 1168
1197 1169 // Move the file to the uploads dir.
1198 - //
1199 - // The attachment url is attacker-reachable — a cloud template can point an
1200 - // image anywhere — so the url-derived name is sanitized, and an extension
1201 - // WordPress does not recognise as an uploadable type is dropped rather than
1202 - // carried into the destination path. fetch_remote_file() re-derives the
1203 - // extension from the response and validates it before writing, so dropping
1204 - // an unusable one here costs nothing.
1205 - $file_name = sanitize_file_name( basename( parse_url( $url, PHP_URL_PATH ) ) );
1206 - if ( pathinfo( $file_name, PATHINFO_EXTENSION ) ) {
1207 - $url_filetype = wp_check_filetype( $file_name );
1208 - if ( empty( $url_filetype['type'] ) ) {
1209 - $file_name = pathinfo( $file_name, PATHINFO_FILENAME );
1210 - }
1211 - }
1170 + $file_name = basename( parse_url( $url, PHP_URL_PATH ) );
1212 1171 $file_name = wp_unique_filename( $upload_dir['path'], $file_name );
1213 1172 $dest_file = $upload_dir['path'] . "/$file_name";
1214 1173 $start = microtime(true);
1215 1174
@@ -1217,13 +1176,8 @@
1217 1176 if ( null === $upload ) {
1218 1177 $upload = $this->fetch_remote_file( $url, $dest_file, $upload_dir );
1219 1178 }
1220 1179
1221 - // Normalize URL scheme to match the current site scheme (fixes HTTP URLs on HTTPS multisites).
1222 - if ( ! is_wp_error( $upload ) && ! empty( $upload['url'] ) ) {
1223 - $upload['url'] = set_url_scheme( $upload['url'] );
1224 - }
1225 -
1226 1180 $end = microtime(true);
1227 1181 $duration = $end - $start;
1228 1182 error_log('Duration: ' . $duration);
1229 1183
@@ -1301,18 +1255,11 @@
1301 1255 }
1302 1256
1303 1257 private function remove_extension($url) {
1304 1258 $parts = pathinfo($url);
1305 - // pathinfo() omits 'extension' for a dotless name, and 'dirname' for an
1306 - // empty one. Remote attachment urls are not obliged to carry an extension
1307 - // (`.../media/1234`), and the url of an attachment that has since been
1308 - // deleted resolves to ''.
1309 - $dirname = isset( $parts['dirname'] ) ? $parts['dirname'] : '';
1310 - $basename = isset( $parts['basename'] ) ? $parts['basename'] : '';
1311 - $extension = isset( $parts['extension'] ) ? $parts['extension'] : '';
1312 - $name = '' === $extension ? $basename : basename($basename, ".{$extension}"); // PATHINFO_FILENAME in PHP 5.2
1259 + $name = basename($parts['basename'], ".{$parts['extension']}"); // PATHINFO_FILENAME in PHP 5.2
1313 1260
1314 - return $dirname . '/' . $name;
1261 + return $parts['dirname'] . '/' . $name;
1315 1262 }
1316 1263
1317 1264 public function set_url_map($original_url, $new_url, $remove_extension = false){
1318 1265 $this->url_remap[ $original_url ] = $new_url;
@@ -1346,13 +1293,13 @@
1346 1293 }
1347 1294
1348 1295 // Fetch the remote URL and write it to the placeholder file.
1349 1296 $attempt = 0;
1350 - $retry_count = $this->attachment_retry_count;
1297 + $retry_count = 3;
1351 1298 $remote_response = null;
1352 1299 do {
1353 1300 $remote_response = wp_safe_remote_get( $url, [
1354 - 'timeout' => $this->attachment_timeout,
1301 + 'timeout' => 300,
1355 1302 'stream' => true,
1356 1303 'filename' => $tmp_file_name,
1357 1304 'headers' => [
1358 1305 'Accept-Encoding' => 'identity',
@@ -1407,18 +1354,12 @@
1407 1354
1408 1355 return new WP_Error( 'import_file_error', sprintf( esc_html__( 'Remote file is too large, limit is %s', 'elementor' ), size_format( $max_size ) ) );
1409 1356 }
1410 1357
1411 - // Override file name with Content-Disposition header value. The header is
1412 - // whatever the remote chose to send, so it is sanitized before it is allowed
1413 - // anywhere near a path: sanitize_file_name() strips directory separators and
1414 - // neutralizes the inner extension of a double-extension name.
1358 + // Override file name with Content-Disposition header value.
1415 1359 if ( ! empty( $headers['content-disposition'] ) ) {
1416 1360 $file_name_from_disposition = self::get_filename_from_disposition( (array) $headers['content-disposition'] );
1417 1361 if ( $file_name_from_disposition ) {
1418 - $file_name_from_disposition = sanitize_file_name( $file_name_from_disposition );
1419 - }
1420 - if ( $file_name_from_disposition ) {
1421 1362 $file_name = $file_name_from_disposition;
1422 1363 }
1423 1364 }
1424 1365
@@ -1442,29 +1383,11 @@
1442 1383 $file_name = $proper_filename;
1443 1384 }
1444 1385
1445 1386 if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
1446 - @unlink( $tmp_file_name );
1447 -
1448 1387 return new WP_Error( 'import_file_error', esc_html__( 'Sorry, this file type is not permitted for security reasons.', 'elementor' ) );
1449 1388 }
1450 1389
1451 - // The type check above ran against $file_name (Content-Disposition), but the
1452 - // write lands on $new_file, whose name came from the request url path. When
1453 - // those two extensions disagree the check guarantees nothing about what is
1454 - // actually written — an image/gif verdict on `ok.gif` would let the bytes go
1455 - // to a `.php` destination. Re-derive the destination from the validated name
1456 - // so the two can never diverge. (CVE-2026-18438)
1457 - if ( $ext && strtolower( pathinfo( $new_file, PATHINFO_EXTENSION ) ) !== strtolower( $ext ) ) {
1458 - $file_name = wp_unique_filename( $uploads['path'], $file_name );
1459 - $new_file = $uploads['path'] . "/$file_name";
1460 - }
1461 -
1462 - // Whatever the branch above decided, the reported name must describe the file
1463 - // that is actually on disk — the remapped url in the imported content is built
1464 - // from it.
1465 - $file_name = wp_basename( $new_file );
1466 -
1467 1390 $move_new_file = copy( $tmp_file_name, $new_file );
1468 1391
1469 1392 if ( ! $move_new_file ) {
1470 1393 @unlink( $tmp_file_name );
@@ -1514,26 +1437,13 @@
1514 1437 if ( isset( self::$_replace_image_ids[ $hash ] ) ) {
1515 1438 return self::$_replace_image_ids[ $hash ];
1516 1439 }
1517 1440
1518 - // Newest row wins. A URL can be claimed by more than one attachment: an
1519 - // AI/customizer image replacement registers the ORIGINAL demo URL against
1520 - // the REPLACEMENT attachment (see process_attachment), while a previous
1521 - // plain import of the same pack already claimed that URL for the original
1522 - // image — and that older row is never cleaned up (clear_old_el_cache only
1523 - // tracks replacement hashes). Without the ordering the stale original wins
1524 - // and the replacement is silently ignored on every re-import.
1525 - //
1526 - // The static cache above short-circuits this, but the import is split
1527 - // across many requests (each SSE `continue` is a fresh PHP process), so
1528 - // the DB is the real source of truth for anything set in an earlier step.
1529 1441 $post_id = $wpdb->get_var(
1530 1442 $wpdb->prepare(
1531 1443 'SELECT `post_id` FROM `' . $wpdb->postmeta . '`
1532 1444 WHERE `meta_key` = \'_elementor_source_image_hash\'
1533 1445 AND `meta_value` = %s
1534 - ORDER BY `meta_id` DESC
1535 - LIMIT 1
1536 1446 ;',
1537 1447 $hash
1538 1448 )
1539 1449 );
@@ -1831,28 +1741,14 @@
1831 1741 return $known_importers;
1832 1742 }
1833 1743
1834 1744 /**
1835 - * There is deliberately NO `parent::__construct()` call here.
1836 - *
1837 - * `WP_Importer` declared an EMPTY constructor up to WordPress 7.0 and
1838 - * removed it in 7.1. Calling a parent constructor the parent does not
1839 - * declare throws `Error: Cannot call constructor`, so the call fataled every
1840 - * Full Site Import on 7.1 — and it had never done anything on the versions
1841 - * where it resolved, because the body was empty.
1842 - *
1843 - * Nothing contained it: `Error` is not an `Exception` in PHP 7+ (siblings
1844 - * under `Throwable`), and the outer import handlers catch `Exception`. The
1845 - * import streams over SSE, so the fatal landed mid-stream with no `error`
1846 - * event: the connection just stopped.
1847 - *
1848 - * Do not "restore" it for symmetry. `tests/unit/Core/Importer/test-WPImportConstruction.php`
1849 - * pins this, and only discriminates on a WP 7.1+ host.
1850 - *
1851 1745 * @param $file
1852 1746 * @param array $args
1853 1747 */
1854 1748 public function __construct( $file, array $args = [] ) {
1749 + parent::__construct();
1750 +
1855 1751 $this->args = $args;
1856 1752 $this->session_id = $args['session_id'];
1857 1753
1858 1754 if ( ! empty( $args['json'] ) ) {
@@ -1869,16 +1765,8 @@
1869 1765 }
1870 1766
1871 1767 if ( ! empty( $this->args['fetch_attachments'] ) ) {
1872 1768 $this->fetch_attachments = true;
1873 - }
1874 -
1875 - if ( isset( $this->args['attachment_timeout'] ) && is_numeric( $this->args['attachment_timeout'] ) ) {
1876 - $this->attachment_timeout = max( 1, (int) $this->args['attachment_timeout'] );
1877 - }
1878 -
1879 - if ( isset( $this->args['attachment_retries'] ) && is_numeric( $this->args['attachment_retries'] ) ) {
1880 - $this->attachment_retry_count = max( 1, (int) $this->args['attachment_retries'] + 1 );
1881 1769 }
1882 1770
1883 1771 if ( isset( $this->args['posts'] ) && is_array( $this->args['posts'] ) ) {
1884 1772 $this->processed_posts = $this->args['posts'];