WXRImporter.php (85925B)
1 <?php 2 namespace ProteusThemes\WPContentImporter2; 3 4 ini_set( 'display_startup_errors', 1 ); 5 ini_set( 'display_errors', 1 ); 6 error_reporting( -1 ); 7 class WXRImporter extends \WP_Importer { 8 9 /** 10 * Maximum supported WXR version 11 */ 12 const MAX_WXR_VERSION = 1.2; 13 14 /** 15 * Regular expression for checking if a post references an attachment 16 * 17 * Note: This is a quick, weak check just to exclude text-only posts. More 18 * vigorous checking is done later to verify. 19 */ 20 const REGEX_HAS_ATTACHMENT_REFS = '! 21 ( 22 # Match anything with an image or attachment class 23 class=[\'"].*?\b(wp-image-\d+|attachment-[\w\-]+)\b 24 | 25 # Match anything that looks like an upload URL 26 src=[\'"][^\'"]*( 27 [0-9]{4}/[0-9]{2}/[^\'"]+\.(jpg|jpeg|png|gif) 28 | 29 content/uploads[^\'"]+ 30 )[\'"] 31 )!ix'; 32 33 /** 34 * Version of WXR we're importing. 35 * 36 * Defaults to 1.0 for compatibility. Typically overridden by a 37 * `<wp:wxr_version>` tag at the start of the file. 38 * 39 * @var string 40 */ 41 protected $version = '1.0'; 42 43 // information to import from WXR file 44 protected $categories = array(); 45 protected $tags = array(); 46 protected $base_url = ''; 47 protected $images_list = array(); 48 // TODO: REMOVE THESE 49 protected $processed_terms = array(); 50 protected $processed_posts = array(); 51 protected $processed_menu_items = array(); 52 protected $menu_item_orphans = array(); 53 protected $missing_menu_items = array(); 54 55 // NEW STYLE 56 protected $mapping = array(); 57 protected $requires_remapping = array(); 58 protected $exists = array(); 59 protected $user_slug_override = array(); 60 61 protected $url_remap = array(); 62 protected $featured_images = array(); 63 // path for image download from local 64 protected $path = ''; 65 // replace link if not image 66 protected $url = ''; 67 68 protected $image_url = ''; 69 70 /** 71 * Logger instance. 72 * 73 * @var WP_Importer_Logger 74 */ 75 protected $logger; 76 77 /** 78 * Constructor 79 * 80 * @param array $options { 81 * @var bool $prefill_existing_posts Should we prefill `post_exists` calls? (True prefills and uses more memory, false checks once per imported post and takes longer. Default is true.) 82 * @var bool $prefill_existing_comments Should we prefill `comment_exists` calls? (True prefills and uses more memory, false checks once per imported comment and takes longer. Default is true.) 83 * @var bool $prefill_existing_terms Should we prefill `term_exists` calls? (True prefills and uses more memory, false checks once per imported term and takes longer. Default is true.) 84 * @var bool $update_attachment_guids Should attachment GUIDs be updated to the new URL? (True updates the GUID, which keeps compatibility with v1, false doesn't update, and allows deduplication and reimporting. Default is false.) 85 * @var bool $fetch_attachments Fetch attachments from the remote server. (True fetches and creates attachment posts, false skips attachments. Default is false.) 86 * @var bool $aggressive_url_search Should we search/replace for URLs aggressively? (True searches all posts' content for old URLs and replaces, false checks for `<img class="wp-image-*">` only. Default is false.) 87 * @var int $default_author User ID to use if author is missing or invalid. (Default is null, which leaves posts unassigned.) 88 * } 89 */ 90 public function __construct( $options = array() ) { 91 // Initialize some important variables 92 $empty_types = array( 93 'post' => array(), 94 'comment' => array(), 95 'term' => array(), 96 'user' => array(), 97 ); 98 99 $this->mapping = $empty_types; 100 $this->mapping['user_slug'] = array(); 101 $this->mapping['term_id'] = array(); 102 $this->requires_remapping = $empty_types; 103 $this->exists = $empty_types; 104 105 $this->options = wp_parse_args( 106 $options, 107 array( 108 'prefill_existing_posts' => true, 109 'prefill_existing_comments' => true, 110 'prefill_existing_terms' => true, 111 'update_attachment_guids' => false, 112 'fetch_attachments' => false, 113 'aggressive_url_search' => false, 114 'default_author' => null, 115 ) 116 ); 117 } 118 119 public function set_logger( $logger ) { 120 $this->logger = $logger; 121 } 122 123 /** 124 * Get a stream reader for the file. 125 * 126 * @param string $file Path to the XML file. 127 * @return XMLReader|WP_Error Reader instance on success, error otherwise. 128 */ 129 protected function get_reader( $file ) { 130 // Avoid loading external entities for security 131 $old_value = null; 132 if ( function_exists( 'libxml_disable_entity_loader' ) ) { 133 // $old_value = libxml_disable_entity_loader( true ); 134 } 135 136 $reader = new \XMLReader(); 137 $status = $reader->open( $file ); 138 139 if ( ! is_null( $old_value ) ) { 140 // libxml_disable_entity_loader( $old_value ); 141 } 142 143 if ( ! $status ) { 144 return new \WP_Error( 'wxr_importer.cannot_parse', __( 'Could not open the file for parsing', 'wordpress-importer' ) ); 145 } 146 147 return $reader; 148 } 149 150 /** 151 * The main controller for the actual import stage. 152 * 153 * @param string $file Path to the WXR file for importing 154 */ 155 public function get_preliminary_information( $file ) { 156 // Let's run the actual importer now, woot 157 $reader = $this->get_reader( $file ); 158 if ( is_wp_error( $reader ) ) { 159 return $reader; 160 } 161 162 // Set the version to compatibility mode first 163 $this->version = '1.0'; 164 165 // Start parsing! 166 $data = new WXRImportInfo(); 167 while ( $reader->read() ) { 168 // Only deal with element opens 169 if ( $reader->nodeType !== \XMLReader::ELEMENT ) { 170 continue; 171 } 172 173 switch ( $reader->name ) { 174 case 'wp:wxr_version': 175 // Upgrade to the correct version 176 $this->version = $reader->readString(); 177 178 if ( version_compare( $this->version, self::MAX_WXR_VERSION, '>' ) ) { 179 $this->logger->warning( 180 sprintf( 181 __( 'This WXR file (version %1$s) is newer than the importer (version %2$s) and may not be supported. Please consider updating.', 'wordpress-importer' ), 182 $this->version, 183 self::MAX_WXR_VERSION 184 ) 185 ); 186 } 187 188 // Handled everything in this node, move on to the next 189 $reader->next(); 190 break; 191 192 case 'generator': 193 $data->generator = $reader->readString(); 194 $reader->next(); 195 break; 196 197 case 'title': 198 $data->title = $reader->readString(); 199 $reader->next(); 200 break; 201 202 case 'wp:base_site_url': 203 $data->siteurl = $reader->readString(); 204 $reader->next(); 205 break; 206 207 case 'wp:base_blog_url': 208 $data->home = $reader->readString(); 209 $reader->next(); 210 break; 211 212 case 'wp:author': 213 $node = $reader->expand(); 214 215 $parsed = $this->parse_author_node( $node ); 216 if ( is_wp_error( $parsed ) ) { 217 $this->log_error( $parsed ); 218 219 // Skip the rest of this post 220 $reader->next(); 221 break; 222 } 223 224 $data->users[] = $parsed; 225 226 // Handled everything in this node, move on to the next 227 $reader->next(); 228 break; 229 230 case 'item': 231 $node = $reader->expand(); 232 $parsed = $this->parse_post_node( $node ); 233 if ( is_wp_error( $parsed ) ) { 234 $this->log_error( $parsed ); 235 236 // Skip the rest of this post 237 $reader->next(); 238 break; 239 } 240 241 if ( $parsed['data']['post_type'] === 'attachment' ) { 242 $data->media_count++; 243 } else { 244 $data->post_count++; 245 } 246 $data->comment_count += count( $parsed['comments'] ); 247 248 // Handled everything in this node, move on to the next 249 $reader->next(); 250 break; 251 252 case 'wp:category': 253 case 'wp:tag': 254 case 'wp:term': 255 $data->term_count++; 256 257 // Handled everything in this node, move on to the next 258 $reader->next(); 259 break; 260 } 261 } 262 263 $data->version = $this->version; 264 265 return $data; 266 } 267 268 /** 269 * The main controller for the actual import stage. 270 * 271 * @param string $file Path to the WXR file for importing 272 */ 273 public function parse_authors( $file ) { 274 // Let's run the actual importer now, woot 275 $reader = $this->get_reader( $file ); 276 if ( is_wp_error( $reader ) ) { 277 return $reader; 278 } 279 280 // Set the version to compatibility mode first 281 $this->version = '1.0'; 282 283 // Start parsing! 284 $authors = array(); 285 while ( $reader->read() ) { 286 // Only deal with element opens 287 if ( $reader->nodeType !== \XMLReader::ELEMENT ) { 288 continue; 289 } 290 291 switch ( $reader->name ) { 292 case 'wp:wxr_version': 293 // Upgrade to the correct version 294 $this->version = $reader->readString(); 295 296 if ( version_compare( $this->version, self::MAX_WXR_VERSION, '>' ) ) { 297 $this->logger->warning( 298 sprintf( 299 __( 'This WXR file (version %1$s) is newer than the importer (version %2$s) and may not be supported. Please consider updating.', 'wordpress-importer' ), 300 $this->version, 301 self::MAX_WXR_VERSION 302 ) 303 ); 304 } 305 306 // Handled everything in this node, move on to the next 307 $reader->next(); 308 break; 309 310 case 'wp:author': 311 $node = $reader->expand(); 312 313 $parsed = $this->parse_author_node( $node ); 314 if ( is_wp_error( $parsed ) ) { 315 $this->log_error( $parsed ); 316 317 // Skip the rest of this post 318 $reader->next(); 319 break; 320 } 321 322 $authors[] = $parsed; 323 324 // Handled everything in this node, move on to the next 325 $reader->next(); 326 break; 327 } 328 } 329 330 return $authors; 331 } 332 333 /** 334 * The main controller for the actual import stage. 335 * 336 * @param string $file Path to the WXR file for importing 337 */ 338 public function import( $file ) { 339 add_filter( 'import_post_meta_key', array( $this, 'is_valid_meta_key' ) ); 340 add_filter( 'http_request_timeout', array( &$this, 'bump_request_timeout' ) ); 341 342 $result = $this->import_start( $file ); 343 if ( is_wp_error( $result ) ) { 344 return $result; 345 } 346 347 // Let's run the actual importer now, woot 348 $reader = $this->get_reader( $file ); 349 if ( is_wp_error( $reader ) ) { 350 return $reader; 351 } 352 353 // Set the version to compatibility mode first 354 $this->version = '1.0'; 355 356 // Reset other variables 357 $this->base_url = ''; 358 359 // Start parsing! 360 while ( $reader->read() ) { 361 // Only deal with element opens 362 if ( $reader->nodeType !== \XMLReader::ELEMENT ) { 363 continue; 364 } 365 366 switch ( $reader->name ) { 367 case 'wp:wxr_version': 368 // Upgrade to the correct version 369 $this->version = $reader->readString(); 370 371 if ( version_compare( $this->version, self::MAX_WXR_VERSION, '>' ) ) { 372 $this->logger->warning( 373 sprintf( 374 __( 'This WXR file (version %1$s) is newer than the importer (version %2$s) and may not be supported. Please consider updating.', 'wordpress-importer' ), 375 $this->version, 376 self::MAX_WXR_VERSION 377 ) 378 ); 379 } 380 381 // Handled everything in this node, move on to the next 382 $reader->next(); 383 break; 384 385 case 'wp:base_site_url': 386 $this->base_url = $reader->readString(); 387 388 // Handled everything in this node, move on to the next 389 $reader->next(); 390 break; 391 392 case 'item': 393 $node = $reader->expand(); 394 $parsed = $this->parse_post_node( $node ); 395 if ( is_wp_error( $parsed ) ) { 396 $this->log_error( $parsed ); 397 398 // Skip the rest of this post 399 $reader->next(); 400 break; 401 } 402 403 $this->process_post( $parsed['data'], $parsed['meta'], $parsed['comments'], $parsed['terms'] ); 404 405 // Handled everything in this node, move on to the next 406 $reader->next(); 407 break; 408 409 case 'wp:author': 410 $node = $reader->expand(); 411 412 $parsed = $this->parse_author_node( $node ); 413 if ( is_wp_error( $parsed ) ) { 414 $this->log_error( $parsed ); 415 416 // Skip the rest of this post 417 $reader->next(); 418 break; 419 } 420 421 $status = $this->process_author( $parsed['data'], $parsed['meta'] ); 422 if ( is_wp_error( $status ) ) { 423 $this->log_error( $status ); 424 } 425 426 // Handled everything in this node, move on to the next 427 $reader->next(); 428 break; 429 430 case 'wp:category': 431 $node = $reader->expand(); 432 433 $parsed = $this->parse_term_node( $node, 'category' ); 434 if ( is_wp_error( $parsed ) ) { 435 $this->log_error( $parsed ); 436 437 // Skip the rest of this post 438 $reader->next(); 439 break; 440 } 441 442 $status = $this->process_term( $parsed['data'], $parsed['meta'] ); 443 444 // Handled everything in this node, move on to the next 445 $reader->next(); 446 break; 447 448 case 'wp:tag': 449 $node = $reader->expand(); 450 451 $parsed = $this->parse_term_node( $node, 'tag' ); 452 if ( is_wp_error( $parsed ) ) { 453 $this->log_error( $parsed ); 454 455 // Skip the rest of this post 456 $reader->next(); 457 break; 458 } 459 460 $status = $this->process_term( $parsed['data'], $parsed['meta'] ); 461 462 // Handled everything in this node, move on to the next 463 $reader->next(); 464 break; 465 466 case 'wp:term': 467 $node = $reader->expand(); 468 469 $parsed = $this->parse_term_node( $node ); 470 if ( is_wp_error( $parsed ) ) { 471 $this->log_error( $parsed ); 472 473 // Skip the rest of this post 474 $reader->next(); 475 break; 476 } 477 478 $status = $this->process_term( $parsed['data'], $parsed['meta'] ); 479 480 // Handled everything in this node, move on to the next 481 $reader->next(); 482 break; 483 484 default: 485 // Skip this node, probably handled by something already 486 break; 487 } 488 } 489 490 // Now that we've done the main processing, do any required 491 // post-processing and remapping. 492 $this->post_process(); 493 494 if ( $this->options['aggressive_url_search'] ) { 495 $this->replace_attachment_urls_in_content(); 496 } 497 498 $this->remap_featured_images(); 499 500 $this->import_end(); 501 } 502 503 /** 504 * Log an error instance to the logger. 505 * 506 * @param WP_Error $error Error instance to log. 507 */ 508 protected function log_error( WP_Error $error ) { 509 $this->logger->warning( $error->get_error_message() ); 510 511 // Log the data as debug info too 512 $data = $error->get_error_data(); 513 if ( ! empty( $data ) ) { 514 $this->logger->debug( var_export( $data, true ) ); 515 } 516 } 517 518 /** 519 * Parses the WXR file and prepares us for the task of processing parsed data 520 * 521 * @param string $file Path to the WXR file for importing 522 */ 523 protected function import_start( $file ) { 524 if ( ! is_file( $file ) ) { 525 return new \WP_Error( 'wxr_importer.file_missing', __( 'The file does not exist, please try again.', 'wordpress-importer' ) ); 526 } 527 528 // Suspend bunches of stuff in WP core 529 wp_defer_term_counting( true ); 530 wp_defer_comment_counting( true ); 531 wp_suspend_cache_invalidation( true ); 532 533 // Prefill exists calls if told to 534 if ( $this->options['prefill_existing_posts'] ) { 535 $this->prefill_existing_posts(); 536 } 537 if ( $this->options['prefill_existing_comments'] ) { 538 $this->prefill_existing_comments(); 539 } 540 if ( $this->options['prefill_existing_terms'] ) { 541 $this->prefill_existing_terms(); 542 } 543 544 /** 545 * Begin the import. 546 * 547 * Fires before the import process has begun. If you need to suspend 548 * caching or heavy processing on hooks, do so here. 549 */ 550 do_action( 'import_start' ); 551 } 552 553 /** 554 * Performs post-import cleanup of files and the cache 555 */ 556 protected function import_end() { 557 // Re-enable stuff in core 558 wp_suspend_cache_invalidation( false ); 559 wp_cache_flush(); 560 foreach ( get_taxonomies() as $tax ) { 561 delete_option( "{$tax}_children" ); 562 _get_term_hierarchy( $tax ); 563 } 564 565 wp_defer_term_counting( false ); 566 wp_defer_comment_counting( false ); 567 568 /** 569 * Complete the import. 570 * 571 * Fires after the import process has finished. If you need to update 572 * your cache or re-enable processing, do so here. 573 */ 574 do_action( 'import_end' ); 575 } 576 577 /** 578 * Set the user mapping. 579 * 580 * @param array $mapping List of map arrays (containing `old_slug`, `old_id`, `new_id`) 581 */ 582 public function set_user_mapping( $mapping ) { 583 foreach ( $mapping as $map ) { 584 if ( empty( $map['old_slug'] ) || empty( $map['old_id'] ) || empty( $map['new_id'] ) ) { 585 $this->logger->warning( __( 'Invalid author mapping', 'wordpress-importer' ) ); 586 $this->logger->debug( var_export( $map, true ) ); 587 continue; 588 } 589 590 $old_slug = $map['old_slug']; 591 $old_id = $map['old_id']; 592 $new_id = $map['new_id']; 593 594 $this->mapping['user'][ $old_id ] = $new_id; 595 $this->mapping['user_slug'][ $old_slug ] = $new_id; 596 } 597 } 598 599 /** 600 * Set the user slug overrides. 601 * 602 * Allows overriding the slug in the import with a custom/renamed version. 603 * 604 * @param string[] $overrides Map of old slug to new slug. 605 */ 606 public function set_user_slug_overrides( $overrides ) { 607 foreach ( $overrides as $original => $renamed ) { 608 $this->user_slug_override[ $original ] = $renamed; 609 } 610 } 611 612 /** 613 * Parse a post node into post data. 614 * 615 * @param DOMElement $node Parent node of post data (typically `item`). 616 * @return array|WP_Error Post data array on success, error otherwise. 617 */ 618 protected function parse_post_node( $node ) { 619 $data = array(); 620 $meta = array(); 621 $comments = array(); 622 $terms = array(); 623 624 foreach ( $node->childNodes as $child ) { 625 // We only care about child elements 626 if ( $child->nodeType !== XML_ELEMENT_NODE ) { 627 continue; 628 } 629 630 switch ( $child->tagName ) { 631 case 'wp:post_type': 632 $data['post_type'] = $child->textContent; 633 break; 634 635 case 'title': 636 $data['post_title'] = $child->textContent; 637 break; 638 639 case 'guid': 640 $data['guid'] = $child->textContent; 641 break; 642 643 case 'dc:creator': 644 $data['post_author'] = $child->textContent; 645 break; 646 647 case 'content:encoded': 648 $data['post_content'] = $child->textContent; 649 break; 650 651 case 'excerpt:encoded': 652 $data['post_excerpt'] = $child->textContent; 653 break; 654 655 case 'wp:post_id': 656 $data['post_id'] = $child->textContent; 657 break; 658 659 case 'wp:post_date': 660 $data['post_date'] = $child->textContent; 661 break; 662 663 case 'wp:post_date_gmt': 664 $data['post_date_gmt'] = $child->textContent; 665 break; 666 667 case 'wp:comment_status': 668 $data['comment_status'] = $child->textContent; 669 break; 670 671 case 'wp:ping_status': 672 $data['ping_status'] = $child->textContent; 673 break; 674 675 case 'wp:post_name': 676 $data['post_name'] = $child->textContent; 677 break; 678 679 case 'wp:status': 680 $data['post_status'] = $child->textContent; 681 682 if ( $data['post_status'] === 'auto-draft' ) { 683 // Bail now 684 return new \WP_Error( 685 'wxr_importer.post.cannot_import_draft', 686 __( 'Cannot import auto-draft posts' ), 687 $data 688 ); 689 } 690 break; 691 692 case 'wp:post_parent': 693 $data['post_parent'] = $child->textContent; 694 break; 695 696 case 'wp:menu_order': 697 $data['menu_order'] = $child->textContent; 698 break; 699 700 case 'wp:post_password': 701 $data['post_password'] = $child->textContent; 702 break; 703 704 case 'wp:is_sticky': 705 $data['is_sticky'] = $child->textContent; 706 break; 707 708 case 'wp:attachment_url': 709 $data['attachment_url'] = $child->textContent; 710 break; 711 712 case 'wp:postmeta': 713 $meta_item = $this->parse_meta_node( $child ); 714 if ( ! empty( $meta_item ) ) { 715 $meta[] = $meta_item; 716 } 717 break; 718 719 case 'wp:comment': 720 $comment_item = $this->parse_comment_node( $child ); 721 if ( ! empty( $comment_item ) ) { 722 $comments[] = $comment_item; 723 } 724 break; 725 726 case 'category': 727 $term_item = $this->parse_category_node( $child ); 728 if ( ! empty( $term_item ) ) { 729 $terms[] = $term_item; 730 } 731 break; 732 } 733 } 734 735 return compact( 'data', 'meta', 'comments', 'terms' ); 736 } 737 738 /** 739 * Create new posts based on import information 740 * 741 * Posts marked as having a parent which doesn't exist will become top level items. 742 * Doesn't create a new post if: the post type doesn't exist, the given post ID 743 * is already noted as imported or a post with the same title and date already exists. 744 * Note that new/updated terms, comments and meta are imported for the last of the above. 745 */ 746 protected function process_post( $data, $meta, $comments, $terms ) { 747 /** 748 * Pre-process post data. 749 * 750 * @param array $data Post data. (Return empty to skip.) 751 * @param array $meta Meta data. 752 * @param array $comments Comments on the post. 753 * @param array $terms Terms on the post. 754 */ 755 $data = apply_filters( 'wxr_importer.pre_process.post', $data, $meta, $comments, $terms ); 756 if ( empty( $data ) ) { 757 return false; 758 } 759 760 $original_id = isset( $data['post_id'] ) ? (int) $data['post_id'] : 0; 761 $parent_id = isset( $data['post_parent'] ) ? (int) $data['post_parent'] : 0; 762 $author_id = isset( $data['post_author'] ) ? (int) $data['post_author'] : 0; 763 764 // Have we already processed this? 765 if ( isset( $this->mapping['post'][ $original_id ] ) ) { 766 return; 767 } 768 769 $post_type_object = get_post_type_object( $data['post_type'] ); 770 771 // Is this type even valid? 772 if ( ! $post_type_object ) { 773 $this->logger->warning( 774 sprintf( 775 __( 'Failed to import "%1$s": Invalid post type %2$s', 'wordpress-importer' ), 776 $data['post_title'], 777 $data['post_type'] 778 ) 779 ); 780 return false; 781 } 782 783 $post_exists = $this->post_exists( $data ); 784 if ( $post_exists ) { 785 $this->logger->info( 786 sprintf( 787 __( '%1$s "%2$s" already exists.', 'wordpress-importer' ), 788 $post_type_object->labels->singular_name, 789 $data['post_title'] 790 ) 791 ); 792 793 // Even though this post already exists, new comments might need importing 794 $this->process_comments( $comments, $original_id, $data, $post_exists ); 795 796 return false; 797 } 798 799 // Map the parent post, or mark it as one we need to fix 800 $requires_remapping = false; 801 if ( $parent_id ) { 802 if ( isset( $this->mapping['post'][ $parent_id ] ) ) { 803 $data['post_parent'] = $this->mapping['post'][ $parent_id ]; 804 } else { 805 $meta[] = array( 806 'key' => '_wxr_import_parent', 807 'value' => $parent_id, 808 ); 809 $requires_remapping = true; 810 811 $data['post_parent'] = 0; 812 } 813 } 814 815 // Map the author, or mark it as one we need to fix 816 $author = sanitize_user( $data['post_author'], true ); 817 if ( empty( $author ) ) { 818 // Missing or invalid author, use default if available. 819 $data['post_author'] = $this->options['default_author']; 820 } elseif ( isset( $this->mapping['user_slug'][ $author ] ) ) { 821 $data['post_author'] = $this->mapping['user_slug'][ $author ]; 822 } else { 823 $meta[] = array( 824 'key' => '_wxr_import_user_slug', 825 'value' => $author, 826 ); 827 $requires_remapping = true; 828 829 $data['post_author'] = (int) get_current_user_id(); 830 } 831 $htmlString = $data['post_content']; 832 833 // Does the post look like it contains attachment images? 834 if ( preg_match( self::REGEX_HAS_ATTACHMENT_REFS, $data['post_content'] ) ) { 835 $meta[] = array( 836 'key' => '_wxr_import_has_attachment_refs', 837 'value' => true, 838 ); 839 $requires_remapping = true; 840 } 841 842 // Whitelist to just the keys we allow 843 $postdata = array( 844 'import_id' => $data['post_id'], 845 ); 846 $allowed = array( 847 'post_author' => true, 848 'post_date' => true, 849 'post_date_gmt' => true, 850 'post_content' => true, 851 'post_excerpt' => true, 852 'post_title' => true, 853 'post_status' => true, 854 'post_name' => true, 855 'comment_status' => true, 856 'ping_status' => true, 857 'guid' => true, 858 'post_parent' => true, 859 'menu_order' => true, 860 'post_type' => true, 861 'post_password' => true, 862 ); 863 foreach ( $data as $key => $value ) { 864 if ( ! isset( $allowed[ $key ] ) ) { 865 continue; 866 } 867 868 $postdata[ $key ] = $data[ $key ]; 869 } 870 871 $postdata = apply_filters( 'wp_import_post_data_processed', $postdata, $data ); 872 $aaa = false; 873 if ( 'attachment' === $postdata['post_type'] ) { 874 if ( ! $this->options['fetch_attachments'] ) { 875 $this->logger->notice( 876 sprintf( 877 __( 'Skipping attachment "%s", fetching attachments disabled' ), 878 $data['post_title'] 879 ) 880 ); 881 return false; 882 } 883 $remote_url = ! empty( $data['attachment_url'] ) ? $data['attachment_url'] : $data['guid']; 884 $remote_url = $this->checkandreturnnewimageurl( $remote_url ); 885 886 // $this->write_log("\n" . 'wp_import_post_data_processed:-' . $remote_url); 887 $post_id = $this->process_attachment( $postdata, $meta, $remote_url ); 888 // echo $post_id ; 889 890 } else { 891 892 if ( preg_match_all( '@src="([^"]+)"@', $htmlString, $match ) ) { 893 $srcs = array_pop( $match ); 894 $domainname = apply_filters( 'pt-ocdi/domain_name', 'smartdata' ); 895 foreach ( $srcs as $src ) { 896 897 if ( $this->strpos_arr( $src, $domainname ) !== false ) { 898 // $this->write_log("\n" . ' remote URL:-' . $src); 899 $htmlString = $this->insert_remote_image( $src, $htmlString ); 900 $data['post_content'] = $htmlString; 901 $postdata['post_content'] = $htmlString; 902 } 903 } 904 } 905 906 $this->url = apply_filters( 'pt-ocdi/replace_url', $this->url ); 907 $site_url = rtrim( site_url() ); 908 foreach ( $this->url as $url ) { 909 $postdata = str_replace( $url, $site_url, $postdata ); 910 } 911 $post_id = wp_insert_post( $postdata, true ); 912 do_action( 'wp_import_insert_post', $post_id, $original_id, $postdata, $data ); 913 } 914 915 if ( is_wp_error( $post_id ) ) { 916 $this->logger->error( 917 sprintf( 918 __( 'Failed to import "%1$s" (%2$s)', 'wordpress-importer' ), 919 $data['post_title'], 920 $post_type_object->labels->singular_name 921 ) 922 ); 923 $this->logger->debug( $post_id->get_error_message() ); 924 925 /** 926 * Post processing failed. 927 * 928 * @param WP_Error $post_id Error object. 929 * @param array $data Raw data imported for the post. 930 * @param array $meta Raw meta data, already processed by {@see process_post_meta}. 931 * @param array $comments Raw comment data, already processed by {@see process_comments}. 932 * @param array $terms Raw term data, already processed. 933 */ 934 do_action( 'wxr_importer.process_failed.post', $post_id, $data, $meta, $comments, $terms ); 935 return false; 936 } 937 938 // Ensure stickiness is handled correctly too 939 if ( $data['is_sticky'] === '1' ) { 940 stick_post( $post_id ); 941 } 942 943 // map pre-import ID to local ID 944 $this->mapping['post'][ $original_id ] = (int) $post_id; 945 if ( $requires_remapping ) { 946 $this->requires_remapping['post'][ $post_id ] = true; 947 } 948 $this->mark_post_exists( $data, $post_id ); 949 950 $this->logger->info( 951 sprintf( 952 __( 'Imported "%1$s" (%2$s)', 'wordpress-importer' ), 953 $data['post_title'], 954 $post_type_object->labels->singular_name 955 ) 956 ); 957 $this->logger->debug( 958 sprintf( 959 __( 'Post %1$d remapped to %2$d', 'wordpress-importer' ), 960 $original_id, 961 $post_id 962 ) 963 ); 964 965 // Handle the terms too 966 $terms = apply_filters( 'wp_import_post_terms', $terms, $post_id, $data ); 967 968 if ( ! empty( $terms ) ) { 969 $term_ids = array(); 970 foreach ( $terms as $term ) { 971 $taxonomy = $term['taxonomy']; 972 $key = sha1( $taxonomy . ':' . $term['slug'] ); 973 974 if ( isset( $this->mapping['term'][ $key ] ) ) { 975 $term_ids[ $taxonomy ][] = (int) $this->mapping['term'][ $key ]; 976 } else { 977 978 /** 979 * Fix for the post format "categories". 980 * The issue in this importer is, that these post formats are misused as categories in WP export 981 * (as the export data <category> item in the post export item), but they are not actually 982 * exported as wp:category items in the XML file, so they need to be inserted on the fly (here). 983 * 984 * Maybe something better can be done in the future? 985 * 986 * Original issue reported here: https://wordpress.org/support/topic/post-format-videoquotegallery-became-format-standard/#post-8447683 987 */ 988 if ( 'post_format' === $taxonomy ) { 989 $term_exists = term_exists( $term['slug'], $taxonomy ); 990 $term_id = is_array( $term_exists ) ? $term_exists['term_id'] : $term_exists; 991 992 if ( empty( $term_id ) ) { 993 $t = wp_insert_term( $term['name'], $taxonomy, array( 'slug' => $term['slug'] ) ); 994 if ( ! is_wp_error( $t ) ) { 995 $term_id = $t['term_id']; 996 $this->mapping['term'][ $key ] = $term_id; 997 } else { 998 $this->logger->warning( 999 sprintf( 1000 esc_html__( 'Failed to import term: %1$s - %2$s', 'wordpress-importer' ), 1001 esc_html( $taxonomy ), 1002 esc_html( $term['name'] ) 1003 ) 1004 ); 1005 continue; 1006 } 1007 } 1008 1009 if ( ! empty( $term_id ) ) { 1010 $term_ids[ $taxonomy ][] = intval( $term_id ); 1011 } 1012 } // End of fix. 1013 else { 1014 $meta[] = array( 1015 'key' => '_wxr_import_term', 1016 'value' => $term, 1017 ); 1018 $requires_remapping = true; 1019 } 1020 } 1021 } 1022 1023 foreach ( $term_ids as $tax => $ids ) { 1024 $tt_ids = wp_set_post_terms( $post_id, $ids, $tax ); 1025 do_action( 'wp_import_set_post_terms', $tt_ids, $ids, $tax, $post_id, $data ); 1026 } 1027 } 1028 1029 $this->process_comments( $comments, $post_id, $data ); 1030 $this->process_post_meta( $meta, $post_id, $data ); 1031 1032 if ( 'nav_menu_item' === $data['post_type'] ) { 1033 $this->process_menu_item_meta( $post_id, $data, $meta ); 1034 } 1035 1036 /** 1037 * Post processing completed. 1038 * 1039 * @param int $post_id New post ID. 1040 * @param array $data Raw data imported for the post. 1041 * @param array $meta Raw meta data, already processed by {@see process_post_meta}. 1042 * @param array $comments Raw comment data, already processed by {@see process_comments}. 1043 * @param array $terms Raw term data, already processed. 1044 */ 1045 do_action( 'wxr_importer.processed.post', $post_id, $data, $meta, $comments, $terms ); 1046 } 1047 1048 public function strpos_arr( $haystack, $needle ) { 1049 if ( ! is_array( $needle ) ) { 1050 $needle = array( $needle ); 1051 } 1052 1053 foreach ( $needle as $what ) { 1054 if ( ( $pos = strpos( $haystack, $what ) ) !== false ) { 1055 return $pos; 1056 } 1057 } 1058 return false; 1059 } 1060 1061 /** 1062 * Attempt to change remote url 1063 * 1064 * @param array $remote_url 1065 */ 1066 public function checkandreturnnewimageurl( $remote_url ) { 1067 if ( strpos( $remote_url, 'wp-content' ) !== false ) { 1068 $urlpath = substr_replace( $remote_url, '', 0, strpos( $remote_url, 'wp-content' ) + 11 ); 1069 $this->path = apply_filters( 'pt-ocdi/destination_path', $this->path ); 1070 // $this->write_log("\n" . 'checkandreturnnewimageurl:-' . $remote_url); 1071 if ( $this->path != '' ) { 1072 1073 if ( file_exists( ABSPATH . $this->path . $urlpath ) ) { 1074 $site_url = rtrim( site_url(), '/' ); 1075 $remote_url = $site_url . $this->path . $urlpath; 1076 // $this->write_log("\n" . 'after_checkandreturnnewimageurl:-' . $remote_url); 1077 } 1078 } 1079 } 1080 return $remote_url; 1081 } 1082 1083 public function isHTML( $string ) { 1084 return $string != strip_tags( $string ) ? true : false; 1085 } 1086 1087 /** 1088 * Attempt to create a new menu item from import data 1089 * 1090 * Fails for draft, orphaned menu items and those without an associated nav_menu 1091 * or an invalid nav_menu term. If the post type or term object which the menu item 1092 * represents doesn't exist then the menu item will not be imported (waits until the 1093 * end of the import to retry again before discarding). 1094 * 1095 * @param array $item Menu item details from WXR file 1096 */ 1097 protected function process_menu_item_meta( $post_id, $data, $meta ) { 1098 1099 $item_type = get_post_meta( $post_id, '_menu_item_type', true ); 1100 $original_object_id = get_post_meta( $post_id, '_menu_item_object_id', true ); 1101 $object_id = null; 1102 1103 $this->logger->debug( sprintf( 'Processing menu item %s', $item_type ) ); 1104 1105 $requires_remapping = false; 1106 switch ( $item_type ) { 1107 case 'taxonomy': 1108 if ( isset( $this->mapping['term_id'][ $original_object_id ] ) ) { 1109 $object_id = $this->mapping['term_id'][ $original_object_id ]; 1110 } else { 1111 add_post_meta( $post_id, '_wxr_import_menu_item', wp_slash( $original_object_id ) ); 1112 $requires_remapping = true; 1113 } 1114 break; 1115 1116 case 'post_type': 1117 if ( isset( $this->mapping['post'][ $original_object_id ] ) ) { 1118 $object_id = $this->mapping['post'][ $original_object_id ]; 1119 } else { 1120 add_post_meta( $post_id, '_wxr_import_menu_item', wp_slash( $original_object_id ) ); 1121 $requires_remapping = true; 1122 } 1123 break; 1124 1125 case 'custom': 1126 // Custom refers to itself, wonderfully easy. 1127 $object_id = $post_id; 1128 break; 1129 1130 default: 1131 // associated object is missing or not imported yet, we'll retry later 1132 $this->missing_menu_items[] = $data; 1133 $this->logger->debug( 'Unknown menu item type' ); 1134 break; 1135 } 1136 1137 if ( $requires_remapping ) { 1138 $this->requires_remapping['post'][ $post_id ] = true; 1139 } 1140 1141 if ( empty( $object_id ) ) { 1142 // Nothing needed here. 1143 return; 1144 } 1145 1146 $this->logger->debug( sprintf( 'Menu item %d mapped to %d', $original_object_id, $object_id ) ); 1147 update_post_meta( $post_id, '_menu_item_object_id', wp_slash( $object_id ) ); 1148 } 1149 1150 /** 1151 * If fetching attachments is enabled then attempt to create a new attachment 1152 * 1153 * @param array $post Attachment post details from WXR 1154 * @param string $url URL to fetch attachment from 1155 * @return int|WP_Error Post ID on success, WP_Error otherwise 1156 */ 1157 1158 public function my_custom_upload_dir( $upload ) { 1159 1160 $urlpath = substr_replace( $this->image_url, '', 0, strpos( $this->image_url, 'uploads' ) + 7 ); 1161 $subdir = substr( $urlpath, 0, strrpos( $urlpath, '/' ) ); 1162 1163 $dir = WP_CONTENT_DIR . '/uploads'; 1164 $url = WP_CONTENT_URL . '/uploads'; 1165 $basedir = $dir; 1166 $baseurl = $url; 1167 $dir .= $subdir; 1168 $url .= $subdir; 1169 $array = array( 1170 'path' => $dir, 1171 'url' => $url, 1172 'subdir' => $subdir, 1173 'basedir' => $basedir, 1174 'baseurl' => $baseurl, 1175 'error' => false, 1176 ); 1177 // error_log(print_r($array, true)); 1178 return $array; 1179 } 1180 protected function process_attachment( $post, $meta, $remote_url ) { 1181 // try to use _wp_attached file for upload folder placement to ensure the same location as the export site 1182 // e.g. location is 2003/05/image.jpg but the attachment post_date is 2010/09, see media_handle_upload() 1183 $post['upload_date'] = $post['post_date']; 1184 foreach ( $meta as $meta_item ) { 1185 if ( $meta_item['key'] !== '_wp_attached_file' ) { 1186 continue; 1187 } 1188 1189 if ( preg_match( '%^[0-9]{4}/[0-9]{2}%', $meta_item['value'], $matches ) ) { 1190 $post['upload_date'] = $matches[0]; 1191 } 1192 break; 1193 } 1194 1195 // if the URL is absolute, but does not contain address, then upload it assuming base_site_url 1196 if ( preg_match( '|^/[\w\W]+$|', $remote_url ) ) { 1197 $remote_url = rtrim( $this->base_url, '/' ) . $remote_url; 1198 } 1199 1200 // echo $remote_url; 1201 // echo "<br>"; 1202 $this->image_url = $remote_url; 1203 if ( strpos( $remote_url, 'revslider' ) !== false ) { 1204 // $this->write_log("\n" . 'revslider=' . $remote_url); 1205 add_filter( 'upload_dir', array( $this, 'my_custom_upload_dir' ) ); 1206 } 1207 1208 $upload = $this->fetch_remote_file( $remote_url, $post ); 1209 if ( strpos( $remote_url, 'revslider' ) !== false ) { 1210 // $this->write_log("\n" . 'revslider=' . $remote_url); 1211 remove_filter( 'upload_dir', array( $this, 'my_custom_upload_dir' ) ); 1212 } 1213 1214 if ( is_wp_error( $upload ) ) { 1215 return $upload; 1216 } 1217 1218 $info = wp_check_filetype( $upload['file'] ); 1219 if ( ! $info ) { 1220 return new \WP_Error( 'attachment_processing_error', __( 'Invalid file type', 'wordpress-importer' ) ); 1221 } 1222 1223 $post['post_mime_type'] = $info['type']; 1224 1225 // WP really likes using the GUID for display. Allow updating it. 1226 // See https://core.trac.wordpress.org/ticket/33386 1227 if ( $this->options['update_attachment_guids'] ) { 1228 $post['guid'] = $upload['url']; 1229 } 1230 1231 // as per wp-admin/includes/upload.php 1232 $post_id = wp_insert_attachment( $post, $upload['file'] ); 1233 if ( is_wp_error( $post_id ) ) { 1234 return $post_id; 1235 } 1236 // $this->write_log("\n" . 'POSTID=' .$post_id .' --- url'. $remote_url); 1237 1238 $attachment_metadata = wp_generate_attachment_metadata( $post_id, $upload['file'] ); 1239 wp_update_attachment_metadata( $post_id, $attachment_metadata ); 1240 1241 // Map this image URL later if we need to 1242 $this->url_remap[ $remote_url ] = $upload['url']; 1243 1244 // If we have a HTTPS URL, ensure the HTTP URL gets replaced too 1245 if ( substr( $remote_url, 0, 8 ) === 'https://' ) { 1246 $insecure_url = 'http' . substr( $remote_url, 5 ); 1247 $this->url_remap[ $insecure_url ] = $upload['url']; 1248 } 1249 1250 if ( $this->options['aggressive_url_search'] ) { 1251 // remap resized image URLs, works by stripping the extension and remapping the URL stub. 1252 /* 1253 if ( preg_match( '!^image/!', $info['type'] ) ) { 1254 $parts = pathinfo( $remote_url ); 1255 $name = basename( $parts['basename'], ".{$parts['extension']}" ); // PATHINFO_FILENAME in PHP 5.2 1256 1257 $parts_new = pathinfo( $upload['url'] ); 1258 $name_new = basename( $parts_new['basename'], ".{$parts_new['extension']}" ); 1259 1260 $this->url_remap[$parts['dirname'] . '/' . $name] = $parts_new['dirname'] . '/' . $name_new; 1261 }*/ 1262 } 1263 update_post_meta( $post_id, '_smartdata_source_image_hash', $this->get_hash_image( $remote_url ) ); 1264 return $post_id; 1265 } 1266 1267 /** 1268 * Parse a meta node into meta data. 1269 * 1270 * @param DOMElement $node Parent node of meta data (typically `wp:postmeta` or `wp:commentmeta`). 1271 * @return array|null Meta data array on success, or null on error. 1272 */ 1273 protected function parse_meta_node( $node ) { 1274 foreach ( $node->childNodes as $child ) { 1275 // We only care about child elements 1276 if ( $child->nodeType !== XML_ELEMENT_NODE ) { 1277 continue; 1278 } 1279 1280 switch ( $child->tagName ) { 1281 case 'wp:meta_key': 1282 $key = $child->textContent; 1283 break; 1284 1285 case 'wp:meta_value': 1286 $value = $child->textContent; 1287 break; 1288 } 1289 } 1290 1291 if ( empty( $key ) || ! isset( $value ) ) { 1292 return null; 1293 } 1294 1295 return compact( 'key', 'value' ); 1296 } 1297 1298 /** 1299 * Process and import post meta items. 1300 * 1301 * @param array $meta List of meta data arrays 1302 * @param int $post_id Post to associate with 1303 * @param array $post Post data 1304 * @return int|WP_Error Number of meta items imported on success, error otherwise. 1305 */ 1306 protected function process_post_meta( $meta, $post_id, $post ) { 1307 if ( empty( $meta ) ) { 1308 return true; 1309 } 1310 1311 // print_r($imageswa); 1312 $domainname = apply_filters( 'pt-ocdi/domain_name', 'smartdata' ); 1313 foreach ( $meta as $meta_item ) { 1314 if ( $meta_item['key'] == '_elementor_data' ) { 1315 preg_match_all( '/"url"\:\"([^\"]+)\"/', $meta_item['value'], $meta_item_values ); 1316 $meta_item_html = $meta_item['value']; 1317 if ( ! empty( $meta_item_values[1] ) ) { 1318 foreach ( $meta_item_values[1] as $meta_item_value ) { 1319 if ( $this->strpos_arr( $meta_item_value, $domainname ) !== false ) { 1320 $meta_item_html_new = $this->replace_remote_uri( $meta_item_value, $meta_item_html ); 1321 $meta_item_html_new = $this->replace_remote_image_uri( $meta_item_value, $meta_item_html ); 1322 if ( $meta_item_html_new ) { 1323 $meta_item_html = $meta_item_html_new; 1324 } 1325 } 1326 } 1327 } 1328 $meta_item['value'] = $meta_item_html; 1329 } 1330 1331 if ( $meta_item['key'] == '_menu_item_url' ) { 1332 // print_r( $meta_item['value']); 1333 if ( $this->strpos_arr( $meta_item['value'], $domainname ) !== false ) { 1334 $meta_item_html = $this->replace_remote_uri( $meta_item['value'], $meta_item['value'] ); 1335 $meta_item['value'] = $meta_item_html; 1336 // print_r("new:-". $meta_item['value']); 1337 } 1338 // exit(); 1339 1340 } 1341 1342 /** 1343 * Pre-process post meta data. 1344 * 1345 * @param array $meta_item Meta data. (Return empty to skip.) 1346 * @param int $post_id Post the meta is attached to. 1347 */ 1348 $meta_item = apply_filters( 'wxr_importer.pre_process.post_meta', $meta_item, $post_id ); 1349 if ( empty( $meta_item ) ) { 1350 return false; 1351 } 1352 1353 $key = apply_filters( 'import_post_meta_key', $meta_item['key'], $post_id, $post ); 1354 $value = false; 1355 1356 if ( '_edit_last' === $key ) { 1357 $value = intval( $meta_item['value'] ); 1358 if ( ! isset( $this->mapping['user'][ $value ] ) ) { 1359 // Skip! 1360 continue; 1361 } 1362 1363 $value = $this->mapping['user'][ $value ]; 1364 } 1365 1366 if ( $key ) { 1367 // export gets meta straight from the DB so could have a serialized string 1368 if ( ! $value ) { 1369 $value = maybe_unserialize( $meta_item['value'] ); 1370 } 1371 1372 add_post_meta( $post_id, wp_slash( $key ), wp_slash_strings_only( $value ) ); 1373 do_action( 'import_post_meta', $post_id, $key, $value ); 1374 1375 // if the post has a featured image, take note of this in case of remap 1376 if ( '_thumbnail_id' === $key ) { 1377 $this->featured_images[ $post_id ] = (int) $value; 1378 } 1379 } 1380 } 1381 1382 return true; 1383 } 1384 1385 private function get_saved_image( $attachment, $metakey ) { 1386 global $wpdb; 1387 1388 $post_id = $wpdb->get_var( 1389 $wpdb->prepare( 1390 'SELECT `post_id` FROM `' . $wpdb->postmeta . '` 1391 WHERE `meta_key` = \'' . $metakey . '\' 1392 AND `meta_value` = %s 1393 ;', 1394 $this->get_hash_image( $attachment ) 1395 ) 1396 ); 1397 1398 if ( $post_id ) { 1399 $new_attachment = wp_get_attachment_url( $post_id ); 1400 return $new_attachment; 1401 } 1402 1403 return false; 1404 } 1405 1406 protected function write_log( $log ) { 1407 if ( true === WP_DEBUG ) { 1408 if ( is_array( $log ) || is_object( $log ) ) { 1409 error_log( print_r( $log, true ) ); 1410 } else { 1411 error_log( $log ); 1412 } 1413 } 1414 } 1415 1416 protected function replace_remote_image_uri( $remote_url, $html ) { 1417 $remote_url_new = stripslashes( $remote_url ); 1418 $this->url = apply_filters( 'pt-ocdi/replace_url', $this->url ); 1419 $site_url = rtrim( site_url(), '/' ); 1420 foreach ( $this->url as $url ) { 1421 if ( strpos( $remote_url_new, $url ) !== false ) { 1422 $urlpath_url = substr_replace( $remote_url_new, '', 0, strpos( $remote_url_new, $url ) + strlen( $url ) + 1 ); 1423 $html = str_replace( $remote_url, addslashes( $site_url . '/' . $urlpath_url ), $html ); 1424 } 1425 } 1426 return $html; 1427 } 1428 1429 protected function replace_remote_uri( $remote_url, $html ) { 1430 1431 $remote_url_new = stripslashes( $remote_url ); 1432 // $this->write_log('replace_remote_uri preocess:-' . remote_url_new); 1433 $this->url = apply_filters( 'pt-ocdi/replace_url', $this->url ); 1434 if ( ! preg_match( '/\.(png|jpeg|jpg|gif|bmp)$/i', $remote_url_new ) && $this->url != '' ) { 1435 $site_url = rtrim( site_url(), '/' ); 1436 foreach ( $this->url as $url ) { 1437 if ( strpos( $remote_url_new, $url ) !== false ) { 1438 $urlpath_url = substr_replace( $remote_url_new, '', 0, strpos( $remote_url_new, $url ) + strlen( $url ) + 1 ); 1439 // $this->write_log('replace_remote_uri preocess:-' . $site_url . '/' . $urlpath_url); 1440 $html = str_replace( $remote_url, addslashes( $site_url . '/' . $urlpath_url ), $html ); 1441 } 1442 } 1443 // $this->write_log("\n" . 'A tag URL:-' . $site_url . '/' . $urlpath_url); 1444 return $html; 1445 } 1446 return false; 1447 } 1448 1449 protected function insert_remote_image( $remote_url, $html, $type = '' ) { 1450 $html_with_replace_link = $this->replace_remote_uri( $remote_url, $html ); 1451 if ( $html_with_replace_link ) { 1452 return $html_with_replace_link; 1453 } 1454 $remote_url_s = stripslashes( $remote_url ); 1455 // $this->write_log("\n" . 'Main URL:-' . $remote_url_s); 1456 if ( strpos( $remote_url_s, 'wp-content' ) !== false ) { 1457 $remote_url_s = $this->checkandreturnnewimageurl( $remote_url_s ); 1458 // $this->write_log('Main URL after preocess:-' . $remote_url_s); 1459 $saved_image = $this->get_saved_image( $remote_url_s, '_smartdata_source_image_hash' ); 1460 // $this->write_log("\n" . 'Save Image:-' . $saved_image); 1461 if ( $saved_image ) { 1462 $html = str_replace( $remote_url, addslashes( $saved_image ), $html ); 1463 // $this->write_log("\n" . 'Replace This Image:-' . $saved_image); 1464 return $html; 1465 } 1466 $file = $this->get_image_temp_file( $remote_url_s ); 1467 if ( $file ) { 1468 $overrides = array( 1469 'test_form' => false, 1470 'test_size' => true, 1471 ); 1472 // Move the temporary file into the uploads directory 1473 $results = $this->move_image_temp_file_to_uploaddir( $file ); 1474 $attachment_id = $this->move_image_uploaddir_to_attachment( $results ); 1475 if ( $attachment_id ) { 1476 $this->upload_image_hash_to_postmeta( $type, $attachment_id, $remote_url ); 1477 if ( ! empty( $results['error'] ) ) { 1478 } else { 1479 $local_url = $results['url']; // URL to the file in the uploads dir 1480 $html = str_replace( $remote_url, addslashes( $local_url ), $html ); 1481 // $this->write_log("\n" . 'New Image Replace:-' . $local_url); 1482 } 1483 return $html; 1484 } 1485 } 1486 } 1487 return $html; 1488 } 1489 1490 private function upload_image_hash_to_postmeta( $type, $attachment_id, $remote_url ) { 1491 1492 if ( $type == 'elementor' ) { 1493 update_post_meta( $attachment_id, '_elementor_source_image_hash', $this->get_hash_image( $remote_url ) ); 1494 update_post_meta( $attachment_id, '_smartdata_source_image_hash', $this->get_hash_image( $remote_url ) ); 1495 } else { 1496 update_post_meta( $attachment_id, '_smartdata_source_image_hash', $this->get_hash_image( $remote_url ) ); 1497 } 1498 } 1499 1500 private function move_image_temp_file_to_uploaddir( $file ) { 1501 1502 $overrides = array( 1503 'test_form' => false, 1504 'test_size' => true, 1505 ); 1506 // Move the temporary file into the uploads directory 1507 return wp_handle_sideload( $file, $overrides ); 1508 } 1509 1510 private function move_image_uploaddir_to_attachment( $results ) { 1511 1512 $wp_upload_dir = wp_upload_dir(); 1513 // Prepare an array of post data for the attachment. 1514 $attachment_data = array( 1515 'guid' => $wp_upload_dir['url'] . '/' . basename( $results['file'] ), 1516 'post_mime_type' => $results['type'], 1517 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $results['file'] ) ), 1518 'post_content' => '', 1519 'post_status' => 'inherit', 1520 ); 1521 $attachment_id = wp_insert_attachment( $attachment_data, $results['file'] ); 1522 if ( ! $attachment_id ) { 1523 return; 1524 } 1525 wp_update_attachment_metadata( 1526 $attachment_id, 1527 wp_generate_attachment_metadata( $attachment_id, $results['file'] ) 1528 ); 1529 return $attachment_id; 1530 } 1531 1532 private function get_image_temp_file( $remote_url ) { 1533 $timeout_seconds = 30; 1534 $temp_file = download_url( $remote_url, $timeout_seconds ); 1535 $ext = pathinfo( $remote_url, PATHINFO_EXTENSION ); 1536 if ( ! is_wp_error( $temp_file ) ) { 1537 $file = array( 1538 'name' => basename( $remote_url ), // ex: wp-header-logo.png 1539 'type' => 'image/' . $ext, 1540 'tmp_name' => $temp_file, 1541 'error' => 0, 1542 'size' => filesize( $temp_file ), 1543 ); 1544 return $file; 1545 } else { 1546 return false; 1547 } 1548 } 1549 1550 private function get_hash_image( $attachment_url ) { 1551 return sha1( $attachment_url ); 1552 } 1553 /** 1554 * Parse a comment node into comment data. 1555 * 1556 * @param DOMElement $node Parent node of comment data (typically `wp:comment`). 1557 * @return array Comment data array. 1558 */ 1559 protected function parse_comment_node( $node ) { 1560 $data = array( 1561 'commentmeta' => array(), 1562 ); 1563 1564 foreach ( $node->childNodes as $child ) { 1565 // We only care about child elements 1566 if ( $child->nodeType !== XML_ELEMENT_NODE ) { 1567 continue; 1568 } 1569 1570 switch ( $child->tagName ) { 1571 case 'wp:comment_id': 1572 $data['comment_id'] = $child->textContent; 1573 break; 1574 case 'wp:comment_author': 1575 $data['comment_author'] = $child->textContent; 1576 break; 1577 1578 case 'wp:comment_author_email': 1579 $data['comment_author_email'] = $child->textContent; 1580 break; 1581 1582 case 'wp:comment_author_IP': 1583 $data['comment_author_IP'] = $child->textContent; 1584 break; 1585 1586 case 'wp:comment_author_url': 1587 $data['comment_author_url'] = $child->textContent; 1588 break; 1589 1590 case 'wp:comment_user_id': 1591 $data['comment_user_id'] = $child->textContent; 1592 break; 1593 1594 case 'wp:comment_date': 1595 $data['comment_date'] = $child->textContent; 1596 break; 1597 1598 case 'wp:comment_date_gmt': 1599 $data['comment_date_gmt'] = $child->textContent; 1600 break; 1601 1602 case 'wp:comment_content': 1603 $data['comment_content'] = $child->textContent; 1604 break; 1605 1606 case 'wp:comment_approved': 1607 $data['comment_approved'] = $child->textContent; 1608 break; 1609 1610 case 'wp:comment_type': 1611 $data['comment_type'] = $child->textContent; 1612 break; 1613 1614 case 'wp:comment_parent': 1615 $data['comment_parent'] = $child->textContent; 1616 break; 1617 1618 case 'wp:commentmeta': 1619 $meta_item = $this->parse_meta_node( $child ); 1620 if ( ! empty( $meta_item ) ) { 1621 $data['commentmeta'][] = $meta_item; 1622 } 1623 break; 1624 } 1625 } 1626 1627 return $data; 1628 } 1629 1630 /** 1631 * Process and import comment data. 1632 * 1633 * @param array $comments List of comment data arrays. 1634 * @param int $post_id Post to associate with. 1635 * @param array $post Post data. 1636 * @return int|WP_Error Number of comments imported on success, error otherwise. 1637 */ 1638 protected function process_comments( $comments, $post_id, $post, $post_exists = false ) { 1639 1640 $comments = apply_filters( 'wp_import_post_comments', $comments, $post_id, $post ); 1641 if ( empty( $comments ) ) { 1642 return 0; 1643 } 1644 1645 $num_comments = 0; 1646 1647 // Sort by ID to avoid excessive remapping later 1648 usort( $comments, array( $this, 'sort_comments_by_id' ) ); 1649 1650 foreach ( $comments as $key => $comment ) { 1651 /** 1652 * Pre-process comment data 1653 * 1654 * @param array $comment Comment data. (Return empty to skip.) 1655 * @param int $post_id Post the comment is attached to. 1656 */ 1657 $comment = apply_filters( 'wxr_importer.pre_process.comment', $comment, $post_id ); 1658 if ( empty( $comment ) ) { 1659 return false; 1660 } 1661 1662 $original_id = isset( $comment['comment_id'] ) ? (int) $comment['comment_id'] : 0; 1663 $parent_id = isset( $comment['comment_parent'] ) ? (int) $comment['comment_parent'] : 0; 1664 $author_id = isset( $comment['comment_user_id'] ) ? (int) $comment['comment_user_id'] : 0; 1665 1666 // if this is a new post we can skip the comment_exists() check 1667 // TODO: Check comment_exists for performance 1668 if ( $post_exists ) { 1669 $existing = $this->comment_exists( $comment ); 1670 if ( $existing ) { 1671 $this->mapping['comment'][ $original_id ] = $existing; 1672 continue; 1673 } 1674 } 1675 1676 // Remove meta from the main array 1677 $meta = isset( $comment['commentmeta'] ) ? $comment['commentmeta'] : array(); 1678 unset( $comment['commentmeta'] ); 1679 1680 // Map the parent comment, or mark it as one we need to fix 1681 $requires_remapping = false; 1682 if ( $parent_id ) { 1683 if ( isset( $this->mapping['comment'][ $parent_id ] ) ) { 1684 $comment['comment_parent'] = $this->mapping['comment'][ $parent_id ]; 1685 } else { 1686 // Prepare for remapping later 1687 $meta[] = array( 1688 'key' => '_wxr_import_parent', 1689 'value' => $parent_id, 1690 ); 1691 $requires_remapping = true; 1692 1693 // Wipe the parent for now 1694 $comment['comment_parent'] = 0; 1695 } 1696 } 1697 1698 // Map the author, or mark it as one we need to fix 1699 if ( $author_id ) { 1700 if ( isset( $this->mapping['user'][ $author_id ] ) ) { 1701 $comment['user_id'] = $this->mapping['user'][ $author_id ]; 1702 } else { 1703 // Prepare for remapping later 1704 $meta[] = array( 1705 'key' => '_wxr_import_user', 1706 'value' => $author_id, 1707 ); 1708 $requires_remapping = true; 1709 1710 // Wipe the user for now 1711 $comment['user_id'] = 0; 1712 } 1713 } 1714 1715 // Run standard core filters 1716 $comment['comment_post_ID'] = $post_id; 1717 $comment = wp_filter_comment( $comment ); 1718 1719 // wp_insert_comment expects slashed data 1720 $comment_id = wp_insert_comment( wp_slash( $comment ) ); 1721 $this->mapping['comment'][ $original_id ] = $comment_id; 1722 if ( $requires_remapping ) { 1723 $this->requires_remapping['comment'][ $comment_id ] = true; 1724 } 1725 $this->mark_comment_exists( $comment, $comment_id ); 1726 1727 /** 1728 * Comment has been imported. 1729 * 1730 * @param int $comment_id New comment ID 1731 * @param array $comment Comment inserted (`comment_id` item refers to the original ID) 1732 * @param int $post_id Post parent of the comment 1733 * @param array $post Post data 1734 */ 1735 do_action( 'wp_import_insert_comment', $comment_id, $comment, $post_id, $post ); 1736 1737 // Process the meta items 1738 foreach ( $meta as $meta_item ) { 1739 $value = maybe_unserialize( $meta_item['value'] ); 1740 add_comment_meta( $comment_id, wp_slash( $meta_item['key'] ), wp_slash( $value ) ); 1741 } 1742 1743 /** 1744 * Post processing completed. 1745 * 1746 * @param int $post_id New post ID. 1747 * @param array $comment Raw data imported for the comment. 1748 * @param array $meta Raw meta data, already processed by {@see process_post_meta}. 1749 * @param array $post_id Parent post ID. 1750 */ 1751 do_action( 'wxr_importer.processed.comment', $comment_id, $comment, $meta, $post_id ); 1752 1753 $num_comments++; 1754 } 1755 1756 return $num_comments; 1757 } 1758 1759 protected function parse_category_node( $node ) { 1760 $data = array( 1761 // Default taxonomy to "category", since this is a `<category>` tag 1762 'taxonomy' => 'category', 1763 ); 1764 $meta = array(); 1765 1766 if ( $node->hasAttribute( 'domain' ) ) { 1767 $data['taxonomy'] = $node->getAttribute( 'domain' ); 1768 } 1769 if ( $node->hasAttribute( 'nicename' ) ) { 1770 $data['slug'] = $node->getAttribute( 'nicename' ); 1771 } 1772 1773 $data['name'] = $node->textContent; 1774 1775 if ( empty( $data['slug'] ) ) { 1776 return null; 1777 } 1778 1779 // Just for extra compatibility 1780 if ( $data['taxonomy'] === 'tag' ) { 1781 $data['taxonomy'] = 'post_tag'; 1782 } 1783 1784 return $data; 1785 } 1786 1787 /** 1788 * Callback for `usort` to sort comments by ID 1789 * 1790 * @param array $a Comment data for the first comment 1791 * @param array $b Comment data for the second comment 1792 * @return int 1793 */ 1794 public static function sort_comments_by_id( $a, $b ) { 1795 if ( empty( $a['comment_id'] ) ) { 1796 return 1; 1797 } 1798 1799 if ( empty( $b['comment_id'] ) ) { 1800 return -1; 1801 } 1802 1803 return $a['comment_id'] - $b['comment_id']; 1804 } 1805 1806 protected function parse_author_node( $node ) { 1807 $data = array(); 1808 $meta = array(); 1809 foreach ( $node->childNodes as $child ) { 1810 // We only care about child elements 1811 if ( $child->nodeType !== XML_ELEMENT_NODE ) { 1812 continue; 1813 } 1814 1815 switch ( $child->tagName ) { 1816 case 'wp:author_login': 1817 $data['user_login'] = $child->textContent; 1818 break; 1819 1820 case 'wp:author_id': 1821 $data['ID'] = $child->textContent; 1822 break; 1823 1824 case 'wp:author_email': 1825 $data['user_email'] = $child->textContent; 1826 break; 1827 1828 case 'wp:author_display_name': 1829 $data['display_name'] = $child->textContent; 1830 break; 1831 1832 case 'wp:author_first_name': 1833 $data['first_name'] = $child->textContent; 1834 break; 1835 1836 case 'wp:author_last_name': 1837 $data['last_name'] = $child->textContent; 1838 break; 1839 } 1840 } 1841 1842 return compact( 'data', 'meta' ); 1843 } 1844 1845 protected function process_author( $data, $meta ) { 1846 /** 1847 * Pre-process user data. 1848 * 1849 * @param array $data User data. (Return empty to skip.) 1850 * @param array $meta Meta data. 1851 */ 1852 $data = apply_filters( 'wxr_importer.pre_process.user', $data, $meta ); 1853 if ( empty( $data ) ) { 1854 return false; 1855 } 1856 1857 // Have we already handled this user? 1858 $original_id = isset( $data['ID'] ) ? $data['ID'] : 0; 1859 $original_slug = $data['user_login']; 1860 1861 if ( isset( $this->mapping['user'][ $original_id ] ) ) { 1862 $existing = $this->mapping['user'][ $original_id ]; 1863 1864 // Note the slug mapping if we need to too 1865 if ( ! isset( $this->mapping['user_slug'][ $original_slug ] ) ) { 1866 $this->mapping['user_slug'][ $original_slug ] = $existing; 1867 } 1868 1869 return false; 1870 } 1871 1872 if ( isset( $this->mapping['user_slug'][ $original_slug ] ) ) { 1873 $existing = $this->mapping['user_slug'][ $original_slug ]; 1874 1875 // Ensure we note the mapping too 1876 $this->mapping['user'][ $original_id ] = $existing; 1877 1878 return false; 1879 } 1880 1881 // Allow overriding the user's slug 1882 $login = $original_slug; 1883 if ( isset( $this->user_slug_override[ $login ] ) ) { 1884 $login = $this->user_slug_override[ $login ]; 1885 } 1886 1887 $userdata = array( 1888 'user_login' => sanitize_user( $login, true ), 1889 'user_pass' => wp_generate_password(), 1890 ); 1891 1892 $allowed = array( 1893 'user_email' => true, 1894 'display_name' => true, 1895 'first_name' => true, 1896 'last_name' => true, 1897 ); 1898 foreach ( $data as $key => $value ) { 1899 if ( ! isset( $allowed[ $key ] ) ) { 1900 continue; 1901 } 1902 1903 $userdata[ $key ] = $data[ $key ]; 1904 } 1905 1906 $user_id = wp_insert_user( wp_slash( $userdata ) ); 1907 if ( is_wp_error( $user_id ) ) { 1908 $this->logger->error( 1909 sprintf( 1910 __( 'Failed to import user "%s"', 'wordpress-importer' ), 1911 $userdata['user_login'] 1912 ) 1913 ); 1914 $this->logger->debug( $user_id->get_error_message() ); 1915 1916 /** 1917 * User processing failed. 1918 * 1919 * @param WP_Error $user_id Error object. 1920 * @param array $userdata Raw data imported for the user. 1921 */ 1922 do_action( 'wxr_importer.process_failed.user', $user_id, $userdata ); 1923 return false; 1924 } 1925 1926 if ( $original_id ) { 1927 $this->mapping['user'][ $original_id ] = $user_id; 1928 } 1929 $this->mapping['user_slug'][ $original_slug ] = $user_id; 1930 1931 $this->logger->info( 1932 sprintf( 1933 __( 'Imported user "%s"', 'wordpress-importer' ), 1934 $userdata['user_login'] 1935 ) 1936 ); 1937 $this->logger->debug( 1938 sprintf( 1939 __( 'User %1$d remapped to %2$d', 'wordpress-importer' ), 1940 $original_id, 1941 $user_id 1942 ) 1943 ); 1944 1945 // TODO: Implement meta handling once WXR includes it 1946 /** 1947 * User processing completed. 1948 * 1949 * @param int $user_id New user ID. 1950 * @param array $userdata Raw data imported for the user. 1951 */ 1952 do_action( 'wxr_importer.processed.user', $user_id, $userdata ); 1953 } 1954 1955 protected function parse_term_node( $node, $type = 'term' ) { 1956 $data = array(); 1957 $meta = array(); 1958 1959 $tag_name = array( 1960 'id' => 'wp:term_id', 1961 'taxonomy' => 'wp:term_taxonomy', 1962 'slug' => 'wp:term_slug', 1963 'parent' => 'wp:term_parent', 1964 'name' => 'wp:term_name', 1965 'description' => 'wp:term_description', 1966 ); 1967 $taxonomy = null; 1968 1969 // Special casing! 1970 switch ( $type ) { 1971 case 'category': 1972 $tag_name['slug'] = 'wp:category_nicename'; 1973 $tag_name['parent'] = 'wp:category_parent'; 1974 $tag_name['name'] = 'wp:cat_name'; 1975 $tag_name['description'] = 'wp:category_description'; 1976 $tag_name['taxonomy'] = null; 1977 1978 $data['taxonomy'] = 'category'; 1979 break; 1980 1981 case 'tag': 1982 $tag_name['slug'] = 'wp:tag_slug'; 1983 $tag_name['parent'] = null; 1984 $tag_name['name'] = 'wp:tag_name'; 1985 $tag_name['description'] = 'wp:tag_description'; 1986 $tag_name['taxonomy'] = null; 1987 1988 $data['taxonomy'] = 'post_tag'; 1989 break; 1990 } 1991 1992 foreach ( $node->childNodes as $child ) { 1993 // We only care about child elements 1994 if ( $child->nodeType !== XML_ELEMENT_NODE ) { 1995 continue; 1996 } 1997 1998 $key = array_search( $child->tagName, $tag_name ); 1999 if ( $key ) { 2000 $data[ $key ] = $child->textContent; 2001 } elseif ( $child->tagName == 'wp:termmeta' ) { 2002 $meta_item = $this->parse_meta_node( $child ); 2003 if ( ! empty( $meta_item ) ) { 2004 $meta[] = $meta_item; 2005 } 2006 } 2007 } 2008 2009 if ( empty( $data['taxonomy'] ) ) { 2010 return null; 2011 } 2012 2013 // Compatibility with WXR 1.0 2014 if ( $data['taxonomy'] === 'tag' ) { 2015 $data['taxonomy'] = 'post_tag'; 2016 } 2017 2018 return compact( 'data', 'meta' ); 2019 } 2020 2021 protected function process_term( $data, $meta ) { 2022 /** 2023 * Pre-process term data. 2024 * 2025 * @param array $data Term data. (Return empty to skip.) 2026 * @param array $meta Meta data. 2027 */ 2028 $data = apply_filters( 'wxr_importer.pre_process.term', $data, $meta ); 2029 if ( empty( $data ) ) { 2030 return false; 2031 } 2032 2033 $original_id = isset( $data['id'] ) ? (int) $data['id'] : 0; 2034 2035 /* 2036 FIX for OCDI! 2037 * As of WP 4.5, export.php returns the SLUG for the term's parent, 2038 * rather than an integer ID (this differs from a post_parent) 2039 * wp_insert_term and wp_update_term use the key: 'parent' and an integer value 'id' 2040 */ 2041 $term_slug = isset( $data['slug'] ) ? $data['slug'] : ''; 2042 $parent_slug = isset( $data['parent'] ) ? $data['parent'] : ''; 2043 2044 $mapping_key = sha1( $data['taxonomy'] . ':' . $data['slug'] ); 2045 $existing = $this->term_exists( $data ); 2046 if ( $existing ) { 2047 $this->mapping['term'][ $mapping_key ] = $existing; 2048 $this->mapping['term_id'][ $original_id ] = $existing; 2049 $this->mapping['term_slug'][ $term_slug ] = $existing; 2050 return false; 2051 } 2052 2053 // WP really likes to repeat itself in export files 2054 if ( isset( $this->mapping['term'][ $mapping_key ] ) ) { 2055 return false; 2056 } 2057 2058 $termdata = array(); 2059 $allowed = array( 2060 'slug' => true, 2061 'description' => true, 2062 'parent' => true, // The parent_id may have already been set, so pass this back to the newly inserted term. 2063 ); 2064 2065 // Map the parent comment, or mark it as one we need to fix 2066 $requires_remapping = false; 2067 if ( $parent_slug ) { 2068 if ( isset( $this->mapping['term_slug'][ $parent_slug ] ) ) { 2069 $data['parent'] = $this->mapping['term_slug'][ $parent_slug ]; 2070 } else { 2071 // Prepare for remapping later 2072 $meta[] = array( 2073 'key' => '_wxr_import_parent', 2074 'value' => $parent_slug, 2075 ); 2076 $requires_remapping = true; 2077 2078 // Wipe the parent id for now 2079 $data['parent'] = 0; 2080 } 2081 } 2082 2083 foreach ( $data as $key => $value ) { 2084 if ( ! isset( $allowed[ $key ] ) ) { 2085 continue; 2086 } 2087 2088 $termdata[ $key ] = $data[ $key ]; 2089 } 2090 2091 $result = wp_insert_term( $data['name'], $data['taxonomy'], $termdata ); 2092 if ( is_wp_error( $result ) ) { 2093 $this->logger->warning( 2094 sprintf( 2095 __( 'Failed to import %1$s %2$s', 'wordpress-importer' ), 2096 $data['taxonomy'], 2097 $data['name'] 2098 ) 2099 ); 2100 $this->logger->debug( $result->get_error_message() ); 2101 do_action( 'wp_import_insert_term_failed', $result, $data ); 2102 2103 /** 2104 * Term processing failed. 2105 * 2106 * @param WP_Error $result Error object. 2107 * @param array $data Raw data imported for the term. 2108 * @param array $meta Meta data supplied for the term. 2109 */ 2110 do_action( 'wxr_importer.process_failed.term', $result, $data, $meta ); 2111 return false; 2112 } 2113 2114 $term_id = $result['term_id']; 2115 2116 // Now prepare to map this new term. 2117 $this->mapping['term'][ $mapping_key ] = $term_id; 2118 $this->mapping['term_id'][ $original_id ] = $term_id; 2119 $this->mapping['term_slug'][ $term_slug ] = $term_id; 2120 2121 /* 2122 * Fix for OCDI! 2123 * The parent will be updated later in post_process_terms 2124 * we will need both the term_id AND the term_taxonomy to retrieve existing 2125 * term attributes. Those attributes will be returned with the corrected parent, 2126 * using wp_update_term. 2127 * Pass both the term_id along with the term_taxonomy as key=>value 2128 * in the requires_remapping['term'] array. 2129 */ 2130 if ( $requires_remapping ) { 2131 $this->requires_remapping['term'][ $term_id ] = $data['taxonomy']; 2132 } 2133 2134 $this->logger->info( 2135 sprintf( 2136 __( 'Imported "%1$s" (%2$s)', 'wordpress-importer' ), 2137 $data['name'], 2138 $data['taxonomy'] 2139 ) 2140 ); 2141 $this->logger->debug( 2142 sprintf( 2143 __( 'Term %1$d remapped to %2$d', 'wordpress-importer' ), 2144 $original_id, 2145 $term_id 2146 ) 2147 ); 2148 2149 // Actuall process of the term meta data. 2150 $this->process_term_meta( $meta, $term_id, $data ); 2151 2152 do_action( 'wp_import_insert_term', $term_id, $data ); 2153 2154 /** 2155 * Term processing completed. 2156 * 2157 * @param int $term_id New term ID. 2158 * @param array $data Raw data imported for the term. 2159 */ 2160 do_action( 'wxr_importer.processed.term', $term_id, $data ); 2161 } 2162 2163 /** 2164 * Process and import term meta items. 2165 * 2166 * @param array $meta List of meta data arrays 2167 * @param int $term_id Term ID to associate with 2168 * @param array $term Term data 2169 * @return int|bool Number of meta items imported on success, false otherwise. 2170 */ 2171 protected function process_term_meta( $meta, $term_id, $term ) { 2172 if ( empty( $meta ) ) { 2173 return true; 2174 } 2175 2176 foreach ( $meta as $meta_item ) { 2177 /** 2178 * Pre-process term meta data. 2179 * 2180 * @param array $meta_item Meta data. (Return empty to skip.) 2181 * @param int $term_id Term the meta is attached to. 2182 */ 2183 $meta_item = apply_filters( 'wxr_importer.pre_process.term_meta', $meta_item, $term_id ); 2184 2185 if ( empty( $meta_item ) ) { 2186 continue; 2187 } 2188 2189 $key = apply_filters( 'import_term_meta_key', $meta_item['key'], $term_id, $term ); 2190 $value = false; 2191 2192 if ( $key ) { 2193 // Export gets meta straight from the DB so could have a serialized string. 2194 if ( ! $value ) { 2195 $value = maybe_unserialize( $meta_item['value'] ); 2196 } 2197 2198 $result = add_term_meta( $term_id, $key, $value ); 2199 2200 if ( is_wp_error( $result ) ) { 2201 $this->logger->warning( 2202 sprintf( 2203 __( 'Failed to add metakey: %1$s, metavalue: %2$s to term_id: %3$d', 'wordpress-importer' ), 2204 $key, 2205 $value, 2206 $term_id 2207 ) 2208 ); 2209 do_action( 'wxr_importer.process_failed.termmeta', $result, $meta_item, $term_id, $term ); 2210 } else { 2211 $this->logger->debug( 2212 sprintf( 2213 __( 'Meta for term_id %1$d : %2$s => %3$s ; successfully added!', 'wordpress-importer' ), 2214 $term_id, 2215 $key, 2216 $value 2217 ) 2218 ); 2219 } 2220 2221 do_action( 'import_term_meta', $term_id, $key, $value ); 2222 } 2223 } 2224 2225 return true; 2226 } 2227 2228 /** 2229 * Attempt to download a remote file attachment 2230 * 2231 * @param string $url URL of item to fetch 2232 * @param array $post Attachment details 2233 * @return array|WP_Error Local file location details on success, WP_Error otherwise 2234 */ 2235 protected function fetch_remote_file( $url, $post ) { 2236 // extract the file name and extension from the url 2237 $file_name = basename( $url ); 2238 2239 // get placeholder file in the upload dir with a unique, sanitized filename 2240 $upload = wp_upload_bits( $file_name, 0, '', $post['upload_date'] ); 2241 // $this->write_log("\n" . 'wp_upload_bits='. $file_name); 2242 2243 if ( $upload['error'] ) { 2244 return new \WP_Error( 'upload_dir_error', $upload['error'] ); 2245 } 2246 2247 // fetch the remote url and write it to the placeholder file 2248 $response = wp_remote_get( 2249 $url, 2250 array( 2251 'stream' => true, 2252 'filename' => $upload['file'], 2253 ) 2254 ); 2255 2256 // request failed 2257 if ( is_wp_error( $response ) ) { 2258 unlink( $upload['file'] ); 2259 return $response; 2260 } 2261 2262 $code = (int) wp_remote_retrieve_response_code( $response ); 2263 2264 // make sure the fetch was successful 2265 if ( $code !== 200 ) { 2266 unlink( $upload['file'] ); 2267 return new \WP_Error( 2268 'import_file_error', 2269 sprintf( 2270 __( 'Remote server returned %1$d %2$s for %3$s', 'wordpress-importer' ), 2271 $code, 2272 get_status_header_desc( $code ), 2273 $url 2274 ) 2275 ); 2276 } 2277 2278 $filesize = filesize( $upload['file'] ); 2279 $headers = wp_remote_retrieve_headers( $response ); 2280 2281 // OCDI fix! 2282 // Smaller images with server compression do not pass this rule. 2283 // More info here: https://github.com/proteusthemes/WordPress-Importer/pull/2 2284 // 2285 // if ( isset( $headers['content-length'] ) && $filesize !== (int) $headers['content-length'] ) { 2286 // unlink( $upload['file'] ); 2287 // return new \WP_Error( 'import_file_error', __( 'Remote file is incorrect size', 'wordpress-importer' ) ); 2288 // } 2289 2290 if ( 0 === $filesize ) { 2291 unlink( $upload['file'] ); 2292 return new \WP_Error( 'import_file_error', __( 'Zero size file downloaded', 'wordpress-importer' ) ); 2293 } 2294 2295 $max_size = (int) $this->max_attachment_size(); 2296 if ( ! empty( $max_size ) && $filesize > $max_size ) { 2297 unlink( $upload['file'] ); 2298 $message = sprintf( __( 'Remote file is too large, limit is %s', 'wordpress-importer' ), size_format( $max_size ) ); 2299 return new \WP_Error( 'import_file_error', $message ); 2300 } 2301 2302 return $upload; 2303 } 2304 2305 protected function post_process() { 2306 // Time to tackle any left-over bits 2307 if ( ! empty( $this->requires_remapping['post'] ) ) { 2308 $this->post_process_posts( $this->requires_remapping['post'] ); 2309 } 2310 if ( ! empty( $this->requires_remapping['comment'] ) ) { 2311 $this->post_process_comments( $this->requires_remapping['comment'] ); 2312 } 2313 if ( ! empty( $this->requires_remapping['term'] ) ) { 2314 $this->post_process_terms( $this->requires_remapping['term'] ); 2315 } 2316 } 2317 2318 protected function post_process_posts( $todo ) { 2319 foreach ( $todo as $post_id => $_ ) { 2320 $this->logger->debug( 2321 sprintf( 2322 // Note: title intentionally not used to skip extra processing 2323 // for when debug logging is off 2324 __( 'Running post-processing for post %d', 'wordpress-importer' ), 2325 $post_id 2326 ) 2327 ); 2328 2329 $data = array(); 2330 2331 $parent_id = get_post_meta( $post_id, '_wxr_import_parent', true ); 2332 if ( ! empty( $parent_id ) ) { 2333 // Have we imported the parent now? 2334 if ( isset( $this->mapping['post'][ $parent_id ] ) ) { 2335 $data['post_parent'] = $this->mapping['post'][ $parent_id ]; 2336 } else { 2337 $this->logger->warning( 2338 sprintf( 2339 __( 'Could not find the post parent for "%1$s" (post #%2$d)', 'wordpress-importer' ), 2340 get_the_title( $post_id ), 2341 $post_id 2342 ) 2343 ); 2344 $this->logger->debug( 2345 sprintf( 2346 __( 'Post %1$d was imported with parent %2$d, but could not be found', 'wordpress-importer' ), 2347 $post_id, 2348 $parent_id 2349 ) 2350 ); 2351 } 2352 } 2353 2354 $author_slug = get_post_meta( $post_id, '_wxr_import_user_slug', true ); 2355 if ( ! empty( $author_slug ) ) { 2356 // Have we imported the user now? 2357 if ( isset( $this->mapping['user_slug'][ $author_slug ] ) ) { 2358 $data['post_author'] = $this->mapping['user_slug'][ $author_slug ]; 2359 } else { 2360 $this->logger->warning( 2361 sprintf( 2362 __( 'Could not find the author for "%1$s" (post #%2$d)', 'wordpress-importer' ), 2363 get_the_title( $post_id ), 2364 $post_id 2365 ) 2366 ); 2367 $this->logger->debug( 2368 sprintf( 2369 __( 'Post %1$d was imported with author "%2$s", but could not be found', 'wordpress-importer' ), 2370 $post_id, 2371 $author_slug 2372 ) 2373 ); 2374 } 2375 } 2376 2377 $has_attachments = get_post_meta( $post_id, '_wxr_import_has_attachment_refs', true ); 2378 if ( ! empty( $has_attachments ) ) { 2379 $post = get_post( $post_id ); 2380 $content = $post->post_content; 2381 2382 // Replace all the URLs we've got 2383 $new_content = str_replace( array_keys( $this->url_remap ), $this->url_remap, $content ); 2384 if ( $new_content !== $content ) { 2385 $data['post_content'] = $new_content; 2386 } 2387 } 2388 2389 if ( get_post_type( $post_id ) === 'nav_menu_item' ) { 2390 $this->post_process_menu_item( $post_id ); 2391 } 2392 2393 // Do we have updates to make? 2394 if ( empty( $data ) ) { 2395 $this->logger->debug( 2396 sprintf( 2397 __( 'Post %d was marked for post-processing, but none was required.', 'wordpress-importer' ), 2398 $post_id 2399 ) 2400 ); 2401 continue; 2402 } 2403 2404 // Run the update 2405 $data['ID'] = $post_id; 2406 $result = wp_update_post( $data, true ); 2407 if ( is_wp_error( $result ) ) { 2408 $this->logger->warning( 2409 sprintf( 2410 __( 'Could not update "%1$s" (post #%2$d) with mapped data', 'wordpress-importer' ), 2411 get_the_title( $post_id ), 2412 $post_id 2413 ) 2414 ); 2415 $this->logger->debug( $result->get_error_message() ); 2416 continue; 2417 } 2418 2419 // Clear out our temporary meta keys 2420 delete_post_meta( $post_id, '_wxr_import_parent' ); 2421 delete_post_meta( $post_id, '_wxr_import_user_slug' ); 2422 delete_post_meta( $post_id, '_wxr_import_has_attachment_refs' ); 2423 } 2424 } 2425 2426 protected function post_process_menu_item( $post_id ) { 2427 $menu_object_id = get_post_meta( $post_id, '_wxr_import_menu_item', true ); 2428 if ( empty( $menu_object_id ) ) { 2429 // No processing needed! 2430 return; 2431 } 2432 2433 $menu_item_type = get_post_meta( $post_id, '_menu_item_type', true ); 2434 switch ( $menu_item_type ) { 2435 case 'taxonomy': 2436 if ( isset( $this->mapping['term_id'][ $menu_object_id ] ) ) { 2437 $menu_object = $this->mapping['term_id'][ $menu_object_id ]; 2438 } 2439 break; 2440 2441 case 'post_type': 2442 if ( isset( $this->mapping['post'][ $menu_object_id ] ) ) { 2443 $menu_object = $this->mapping['post'][ $menu_object_id ]; 2444 } 2445 break; 2446 2447 default: 2448 // Cannot handle this. 2449 return; 2450 } 2451 2452 if ( ! empty( $menu_object ) ) { 2453 update_post_meta( $post_id, '_menu_item_object_id', wp_slash( $menu_object ) ); 2454 } else { 2455 $this->logger->warning( 2456 sprintf( 2457 __( 'Could not find the menu object for "%1$s" (post #%2$d)', 'wordpress-importer' ), 2458 get_the_title( $post_id ), 2459 $post_id 2460 ) 2461 ); 2462 $this->logger->debug( 2463 sprintf( 2464 __( 'Post %1$d was imported with object "%2$d" of type "%3$s", but could not be found', 'wordpress-importer' ), 2465 $post_id, 2466 $menu_object_id, 2467 $menu_item_type 2468 ) 2469 ); 2470 } 2471 2472 delete_post_meta( $post_id, '_wxr_import_menu_item' ); 2473 } 2474 2475 protected function post_process_comments( $todo ) { 2476 foreach ( $todo as $comment_id => $_ ) { 2477 $data = array(); 2478 2479 $parent_id = get_comment_meta( $comment_id, '_wxr_import_parent', true ); 2480 if ( ! empty( $parent_id ) ) { 2481 // Have we imported the parent now? 2482 if ( isset( $this->mapping['comment'][ $parent_id ] ) ) { 2483 $data['comment_parent'] = $this->mapping['comment'][ $parent_id ]; 2484 } else { 2485 $this->logger->warning( 2486 sprintf( 2487 __( 'Could not find the comment parent for comment #%d', 'wordpress-importer' ), 2488 $comment_id 2489 ) 2490 ); 2491 $this->logger->debug( 2492 sprintf( 2493 __( 'Comment %1$d was imported with parent %2$d, but could not be found', 'wordpress-importer' ), 2494 $comment_id, 2495 $parent_id 2496 ) 2497 ); 2498 } 2499 } 2500 2501 $author_id = get_comment_meta( $comment_id, '_wxr_import_user', true ); 2502 if ( ! empty( $author_id ) ) { 2503 // Have we imported the user now? 2504 if ( isset( $this->mapping['user'][ $author_id ] ) ) { 2505 $data['user_id'] = $this->mapping['user'][ $author_id ]; 2506 } else { 2507 $this->logger->warning( 2508 sprintf( 2509 __( 'Could not find the author for comment #%d', 'wordpress-importer' ), 2510 $comment_id 2511 ) 2512 ); 2513 $this->logger->debug( 2514 sprintf( 2515 __( 'Comment %1$d was imported with author %2$d, but could not be found', 'wordpress-importer' ), 2516 $comment_id, 2517 $author_id 2518 ) 2519 ); 2520 } 2521 } 2522 2523 // Do we have updates to make? 2524 if ( empty( $data ) ) { 2525 continue; 2526 } 2527 2528 // Run the update 2529 $data['comment_ID'] = $comment_ID; 2530 $result = wp_update_comment( wp_slash( $data ) ); 2531 if ( empty( $result ) ) { 2532 $this->logger->warning( 2533 sprintf( 2534 __( 'Could not update comment #%d with mapped data', 'wordpress-importer' ), 2535 $comment_id 2536 ) 2537 ); 2538 continue; 2539 } 2540 2541 // Clear out our temporary meta keys 2542 delete_comment_meta( $comment_id, '_wxr_import_parent' ); 2543 delete_comment_meta( $comment_id, '_wxr_import_user' ); 2544 } 2545 } 2546 2547 /** 2548 * There is no explicit 'top' or 'root' for a hierarchy of WordPress terms 2549 * Terms without a parent, or parent=0 are either unconnected (orphans) 2550 * or top-level siblings without an explicit root parent 2551 * An unconnected term (orphan) should have a null parent_slug 2552 * Top-level siblings without an explicit root parent, shall be identified 2553 * with the parent_slug: top 2554 * [we'll map parent_slug: top into parent 0] 2555 */ 2556 protected function post_process_terms( $terms_to_be_remapped ) { 2557 $this->mapping['term_slug']['top'] = 0; 2558 // The term_id and term_taxonomy are passed-in with $this->requires_remapping['term']. 2559 foreach ( $terms_to_be_remapped as $termid => $term_taxonomy ) { 2560 // Basic check. 2561 if ( empty( $termid ) || ! is_numeric( $termid ) ) { 2562 $this->logger->warning( 2563 sprintf( 2564 __( 'Faulty term_id provided in terms-to-be-remapped array %s', 'wordpress-importer' ), 2565 $termid 2566 ) 2567 ); 2568 continue; 2569 } 2570 // This cast to integer may be unnecessary. 2571 $term_id = (int) $termid; 2572 2573 if ( empty( $term_taxonomy ) ) { 2574 $this->logger->warning( 2575 sprintf( 2576 __( 'No taxonomy provided in terms-to-be-remapped array for term #%d', 'wordpress-importer' ), 2577 $term_id 2578 ) 2579 ); 2580 continue; 2581 } 2582 2583 $data = array(); 2584 $parent_slug = get_term_meta( $term_id, '_wxr_import_parent', true ); 2585 2586 if ( empty( $parent_slug ) ) { 2587 $this->logger->warning( 2588 sprintf( 2589 __( 'No parent_slug identified in remapping-array for term: %d', 'wordpress-importer' ), 2590 $term_id 2591 ) 2592 ); 2593 continue; 2594 } 2595 2596 if ( ! isset( $this->mapping['term_slug'][ $parent_slug ] ) || ! is_numeric( $this->mapping['term_slug'][ $parent_slug ] ) ) { 2597 $this->logger->warning( 2598 sprintf( 2599 __( 'The term(%1$d)"s parent_slug (%2$s) is not found in the remapping-array.', 'wordpress-importer' ), 2600 $term_id, 2601 $parent_slug 2602 ) 2603 ); 2604 continue; 2605 } 2606 2607 $mapped_parent = (int) $this->mapping['term_slug'][ $parent_slug ]; 2608 2609 $termattributes = get_term_by( 'id', $term_id, $term_taxonomy, ARRAY_A ); 2610 // Note: the default OBJECT return results in a reserved-word clash with 'parent' [$termattributes->parent], so instead return an associative array. 2611 2612 if ( empty( $termattributes ) ) { 2613 $this->logger->warning( 2614 sprintf( 2615 __( 'No data returned by get_term_by for term_id #%d', 'wordpress-importer' ), 2616 $term_id 2617 ) 2618 ); 2619 continue; 2620 } 2621 // Check if the correct parent id is already correctly mapped. 2622 if ( isset( $termattributes['parent'] ) && $termattributes['parent'] == $mapped_parent ) { 2623 // Clear out our temporary meta key. 2624 delete_term_meta( $term_id, '_wxr_import_parent' ); 2625 continue; 2626 } 2627 2628 // Otherwise set the mapped parent and update the term. 2629 $termattributes['parent'] = $mapped_parent; 2630 2631 $result = wp_update_term( $term_id, $termattributes['taxonomy'], $termattributes ); 2632 2633 if ( is_wp_error( $result ) ) { 2634 $this->logger->warning( 2635 sprintf( 2636 __( 'Could not update "%1$s" (term #%2$d) with mapped data', 'wordpress-importer' ), 2637 $termattributes['name'], 2638 $term_id 2639 ) 2640 ); 2641 $this->logger->debug( $result->get_error_message() ); 2642 continue; 2643 } 2644 // Clear out our temporary meta key. 2645 delete_term_meta( $term_id, '_wxr_import_parent' ); 2646 $this->logger->debug( 2647 sprintf( 2648 __( 'Term %1$d was successfully updated with parent %2$d', 'wordpress-importer' ), 2649 $term_id, 2650 $mapped_parent 2651 ) 2652 ); 2653 } 2654 } 2655 2656 /** 2657 * Use stored mapping information to update old attachment URLs 2658 */ 2659 protected function replace_attachment_urls_in_content() { 2660 global $wpdb; 2661 // make sure we do the longest urls first, in case one is a substring of another 2662 uksort( $this->url_remap, array( $this, 'cmpr_strlen' ) ); 2663 2664 foreach ( $this->url_remap as $from_url => $to_url ) { 2665 // remap urls in post_content 2666 $query = $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s)", $from_url, $to_url ); 2667 $wpdb->query( $query ); 2668 2669 // remap enclosure urls 2670 $query = $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key='enclosure'", $from_url, $to_url ); 2671 $result = $wpdb->query( $query ); 2672 } 2673 } 2674 2675 /** 2676 * Update _thumbnail_id meta to new, imported attachment IDs 2677 */ 2678 function remap_featured_images() { 2679 if ( empty( $this->featured_images ) ) { 2680 return; 2681 } 2682 2683 $this->logger->info( esc_html__( 'Starting remapping of featured images', 'wordpress-importer' ) ); 2684 2685 // Cycle through posts that have a featured image. 2686 foreach ( $this->featured_images as $post_id => $value ) { 2687 if ( isset( $this->mapping['post'][ $value ] ) ) { 2688 $new_id = $this->mapping['post'][ $value ]; 2689 2690 // Only update if there's a difference. 2691 if ( $new_id !== $value ) { 2692 $this->logger->info( sprintf( esc_html__( 'Remapping featured image ID %1$d to new ID %2$d for post ID %3$d', 'wordpress-importer' ), $value, $new_id, $post_id ) ); 2693 2694 update_post_meta( $post_id, '_thumbnail_id', $new_id ); 2695 } 2696 } 2697 } 2698 } 2699 2700 /** 2701 * Decide if the given meta key maps to information we will want to import 2702 * 2703 * @param string $key The meta key to check 2704 * @return string|bool The key if we do want to import, false if not 2705 */ 2706 public function is_valid_meta_key( $key ) { 2707 // skip attachment metadata since we'll regenerate it from scratch 2708 // skip _edit_lock as not relevant for import 2709 if ( in_array( $key, array( '_wp_attached_file', '_wp_attachment_metadata', '_edit_lock' ) ) ) { 2710 return false; 2711 } 2712 2713 return $key; 2714 } 2715 2716 /** 2717 * Decide what the maximum file size for downloaded attachments is. 2718 * Default is 0 (unlimited), can be filtered via import_attachment_size_limit 2719 * 2720 * @return int Maximum attachment file size to import 2721 */ 2722 protected function max_attachment_size() { 2723 return apply_filters( 'import_attachment_size_limit', 0 ); 2724 } 2725 2726 /** 2727 * Added to http_request_timeout filter to force timeout at 60 seconds during import 2728 * 2729 * @access protected 2730 * @return int 60 2731 */ 2732 function bump_request_timeout( $val ) { 2733 return 60; 2734 } 2735 2736 // return the difference in length between two strings 2737 function cmpr_strlen( $a, $b ) { 2738 return strlen( $b ) - strlen( $a ); 2739 } 2740 2741 /** 2742 * Prefill existing post data. 2743 * 2744 * This preloads all GUIDs into memory, allowing us to avoid hitting the 2745 * database when we need to check for existence. With larger imports, this 2746 * becomes prohibitively slow to perform SELECT queries on each. 2747 * 2748 * By preloading all this data into memory, it's a constant-time lookup in 2749 * PHP instead. However, this does use a lot more memory, so for sites doing 2750 * small imports onto a large site, it may be a better tradeoff to use 2751 * on-the-fly checking instead. 2752 */ 2753 protected function prefill_existing_posts() { 2754 global $wpdb; 2755 $posts = $wpdb->get_results( "SELECT ID, guid FROM {$wpdb->posts}" ); 2756 2757 foreach ( $posts as $item ) { 2758 $this->exists['post'][ $item->guid ] = $item->ID; 2759 } 2760 } 2761 2762 /** 2763 * Does the post exist? 2764 * 2765 * @param array $data Post data to check against. 2766 * @return int|bool Existing post ID if it exists, false otherwise. 2767 */ 2768 protected function post_exists( $data ) { 2769 // Constant-time lookup if we prefilled 2770 $exists_key = $data['guid']; 2771 2772 if ( $this->options['prefill_existing_posts'] ) { 2773 // OCDI: fix for custom post types. The guids in the prefilled section are escaped, so these ones should be as well. 2774 $exists_key = htmlentities( $exists_key ); 2775 return isset( $this->exists['post'][ $exists_key ] ) ? $this->exists['post'][ $exists_key ] : false; 2776 } 2777 2778 // No prefilling, but might have already handled it 2779 if ( isset( $this->exists['post'][ $exists_key ] ) ) { 2780 return $this->exists['post'][ $exists_key ]; 2781 } 2782 2783 // Still nothing, try post_exists, and cache it 2784 $exists = post_exists( $data['post_title'], $data['post_content'], $data['post_date'] ); 2785 $this->exists['post'][ $exists_key ] = $exists; 2786 2787 return $exists; 2788 } 2789 2790 /** 2791 * Mark the post as existing. 2792 * 2793 * @param array $data Post data to mark as existing. 2794 * @param int $post_id Post ID. 2795 */ 2796 protected function mark_post_exists( $data, $post_id ) { 2797 $exists_key = $data['guid']; 2798 $this->exists['post'][ $exists_key ] = $post_id; 2799 } 2800 2801 /** 2802 * Prefill existing comment data. 2803 * 2804 * @see self::prefill_existing_posts() for justification of why this exists. 2805 */ 2806 protected function prefill_existing_comments() { 2807 global $wpdb; 2808 $posts = $wpdb->get_results( "SELECT comment_ID, comment_author, comment_date FROM {$wpdb->comments}" ); 2809 2810 foreach ( $posts as $item ) { 2811 $exists_key = sha1( $item->comment_author . ':' . $item->comment_date ); 2812 $this->exists['comment'][ $exists_key ] = $item->comment_ID; 2813 } 2814 } 2815 2816 /** 2817 * Does the comment exist? 2818 * 2819 * @param array $data Comment data to check against. 2820 * @return int|bool Existing comment ID if it exists, false otherwise. 2821 */ 2822 protected function comment_exists( $data ) { 2823 $exists_key = sha1( $data['comment_author'] . ':' . $data['comment_date'] ); 2824 2825 // Constant-time lookup if we prefilled 2826 if ( $this->options['prefill_existing_comments'] ) { 2827 return isset( $this->exists['comment'][ $exists_key ] ) ? $this->exists['comment'][ $exists_key ] : false; 2828 } 2829 2830 // No prefilling, but might have already handled it 2831 if ( isset( $this->exists['comment'][ $exists_key ] ) ) { 2832 return $this->exists['comment'][ $exists_key ]; 2833 } 2834 2835 // Still nothing, try comment_exists, and cache it 2836 $exists = comment_exists( $data['comment_author'], $data['comment_date'] ); 2837 $this->exists['comment'][ $exists_key ] = $exists; 2838 2839 return $exists; 2840 } 2841 2842 /** 2843 * Mark the comment as existing. 2844 * 2845 * @param array $data Comment data to mark as existing. 2846 * @param int $comment_id Comment ID. 2847 */ 2848 protected function mark_comment_exists( $data, $comment_id ) { 2849 $exists_key = sha1( $data['comment_author'] . ':' . $data['comment_date'] ); 2850 $this->exists['comment'][ $exists_key ] = $comment_id; 2851 } 2852 2853 /** 2854 * Prefill existing term data. 2855 * 2856 * @see self::prefill_existing_posts() for justification of why this exists. 2857 */ 2858 protected function prefill_existing_terms() { 2859 global $wpdb; 2860 $query = "SELECT t.term_id, tt.taxonomy, t.slug FROM {$wpdb->terms} AS t"; 2861 $query .= " JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id"; 2862 $terms = $wpdb->get_results( $query ); 2863 2864 foreach ( $terms as $item ) { 2865 $exists_key = sha1( $item->taxonomy . ':' . $item->slug ); 2866 $this->exists['term'][ $exists_key ] = $item->term_id; 2867 } 2868 } 2869 2870 /** 2871 * Does the term exist? 2872 * 2873 * @param array $data Term data to check against. 2874 * @return int|bool Existing term ID if it exists, false otherwise. 2875 */ 2876 protected function term_exists( $data ) { 2877 $exists_key = sha1( $data['taxonomy'] . ':' . $data['slug'] ); 2878 2879 // Constant-time lookup if we prefilled 2880 if ( $this->options['prefill_existing_terms'] ) { 2881 return isset( $this->exists['term'][ $exists_key ] ) ? $this->exists['term'][ $exists_key ] : false; 2882 } 2883 2884 // No prefilling, but might have already handled it 2885 if ( isset( $this->exists['term'][ $exists_key ] ) ) { 2886 return $this->exists['term'][ $exists_key ]; 2887 } 2888 2889 // Still nothing, try comment_exists, and cache it 2890 $exists = term_exists( $data['slug'], $data['taxonomy'] ); 2891 if ( is_array( $exists ) ) { 2892 $exists = $exists['term_id']; 2893 } 2894 2895 $this->exists['term'][ $exists_key ] = $exists; 2896 2897 return $exists; 2898 } 2899 2900 /** 2901 * Mark the term as existing. 2902 * 2903 * @param array $data Term data to mark as existing. 2904 * @param int $term_id Term ID. 2905 */ 2906 protected function mark_term_exists( $data, $term_id ) { 2907 $exists_key = sha1( $data['taxonomy'] . ':' . $data['slug'] ); 2908 $this->exists['term'][ $exists_key ] = $term_id; 2909 } 2910 }