| @@ -6,8 +6,12 @@ | ||
| 6 | 6 | * @since 4.8.0 |
| 7 | 7 | * @author Automattic |
| 8 | 8 | */ |
| 9 | 9 | |
| 10 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 11 | + exit( 0 ); | |
| 12 | +} | |
| 13 | + | |
| 10 | 14 | /* Include sitemap subclasses, if not already, and include proper buffer based on phpxml's availability. */ |
| 11 | 15 | require_once __DIR__ . '/sitemap-constants.php'; |
| 12 | 16 | require_once __DIR__ . '/sitemap-buffer.php'; |
| 13 | 17 | |
| @@ -85,8 +89,24 @@ | ||
| 85 | 89 | */ |
| 86 | 90 | class Jetpack_Sitemap_Builder { // phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound,Generic.Classes.OpeningBraceSameLine.ContentAfterBrace |
| 87 | 91 | |
| 88 | 92 | /** |
| 93 | + * Returned by the master sitemap builders when the entries do not fit in one | |
| 94 | + * buffer, so the flat listing has to give way to the nested indexes. | |
| 95 | + * | |
| 96 | + * @since 16.2 | |
| 97 | + */ | |
| 98 | + const MASTER_OVERFLOW = 'overflow'; | |
| 99 | + | |
| 100 | + /** | |
| 101 | + * Returned by the master sitemap builders when a sitemap file they would link | |
| 102 | + * is missing, so any master built now would reach fewer URLs than the last one. | |
| 103 | + * | |
| 104 | + * @since 16.2 | |
| 105 | + */ | |
| 106 | + const MASTER_INCOMPLETE = 'incomplete'; | |
| 107 | + | |
| 108 | + /** | |
| 89 | 109 | * Librarian object for storing and retrieving sitemap data. |
| 90 | 110 | * |
| 91 | 111 | * @access private |
| 92 | 112 | * @since 4.8.0 |
| @@ -159,9 +179,9 @@ | ||
| 159 | 179 | $this->logger->report( '-- Updating...' ); |
| 160 | 180 | if ( ! class_exists( 'DOMDocument' ) ) { |
| 161 | 181 | $this->logger->report( |
| 162 | 182 | __( |
| 163 | - 'Jetpack can not load necessary XML manipulation libraries. Please ask your hosting provider to refer to our server requirements at https://jetpack.com/support/server-requirements/ .', | |
| 183 | + 'Jetpack cannot load necessary XML manipulation libraries. Please ask your hosting provider to refer to our server requirements at https://jetpack.com/support/server-requirements/ .', | |
| 164 | 184 | 'jetpack' |
| 165 | 185 | ), |
| 166 | 186 | true |
| 167 | 187 | ); |
| @@ -167,8 +187,23 @@ | ||
| 167 | 187 | ); |
| 168 | 188 | } |
| 169 | 189 | } |
| 170 | 190 | |
| 191 | + /** | |
| 192 | + * Filters whether to suspend cache addition for the entire sitemap generation. | |
| 193 | + * | |
| 194 | + * @since 15.0 | |
| 195 | + * | |
| 196 | + * @param bool|null $suspend_addition Whether to suspend cache addition. Defaults to null. | |
| 197 | + * @return bool|null Whether to suspend cache addition. | |
| 198 | + */ | |
| 199 | + $suspend_addition = apply_filters( 'jetpack_sitemap_suspend_cache_addition', null ); | |
| 200 | + | |
| 201 | + // Cache the previous state in case something else changed it. | |
| 202 | + $prev_suspend_addition = wp_suspend_cache_addition(); | |
| 203 | + | |
| 204 | + wp_suspend_cache_addition( $suspend_addition ); | |
| 205 | + | |
| 171 | 206 | for ( $i = 1; $i <= JP_SITEMAP_UPDATE_SIZE; $i++ ) { |
| 172 | 207 | if ( true === $this->build_next_sitemap_file() ) { |
| 173 | 208 | break; // All finished! |
| 174 | 209 | } |
| @@ -173,8 +208,11 @@ | ||
| 173 | 208 | break; // All finished! |
| 174 | 209 | } |
| 175 | 210 | } |
| 176 | 211 | |
| 212 | + // Restore previous state. | |
| 213 | + wp_suspend_cache_addition( $prev_suspend_addition ); | |
| 214 | + | |
| 177 | 215 | if ( $this->logger ) { |
| 178 | 216 | $this->logger->report( '-- ...done for now.' ); |
| 179 | 217 | $this->logger->time(); |
| 180 | 218 | } |
| @@ -374,10 +412,12 @@ | ||
| 374 | 412 | */ |
| 375 | 413 | private function build_next_sitemap_index_of_type( $index_type, $next_type, $state ) { |
| 376 | 414 | $sitemap_type = jp_sitemap_child_type_of( $index_type ); |
| 377 | 415 | |
| 416 | + $sitemap_type_exists = isset( $state['max'][ $sitemap_type ] ) && is_array( $state['max'][ $sitemap_type ] ); | |
| 417 | + | |
| 378 | 418 | // If only 0 or 1 sitemaps were built, advance to the next type and return. |
| 379 | - if ( 1 >= $state['max'][ $sitemap_type ]['number'] ) { | |
| 419 | + if ( $sitemap_type_exists && 1 >= $state['max'][ $sitemap_type ]['number'] ) { | |
| 380 | 420 | Jetpack_Sitemap_State::check_in( |
| 381 | 421 | array( |
| 382 | 422 | 'sitemap-type' => $next_type, |
| 383 | 423 | 'last-added' => 0, |
| @@ -469,96 +509,304 @@ | ||
| 469 | 509 | |
| 470 | 510 | /** |
| 471 | 511 | * Builds the master sitemap index. |
| 472 | 512 | * |
| 513 | + * A sitemap index file may not list other sitemap index files, so with the | |
| 514 | + * `jetpack_sitemap_flat_master_index` filter on, the master lists every | |
| 515 | + * individual sitemap file directly whenever they all fit in one buffer. They | |
| 516 | + * no longer fit somewhere north of a million URLs, and only then does it fall | |
| 517 | + * back to linking the per-type `*-sitemap-index-N.xml` files. With the filter | |
| 518 | + * off, which is still the default, it always links them. | |
| 519 | + * | |
| 520 | + * Either way the buffer is only stored once every sitemap this generation | |
| 521 | + * cycle recorded is accounted for. If one is missing, the master the previous | |
| 522 | + * cycle stored is left in place: it reaches more URLs than anything that | |
| 523 | + * could be built right now. | |
| 524 | + * | |
| 525 | + * @link https://www.sitemaps.org/protocol.html#index | |
| 526 | + * | |
| 473 | 527 | * @param array $max Array of sitemap types with max index and datetime. |
| 474 | 528 | * |
| 475 | 529 | * @since 4.8.0 |
| 476 | 530 | */ |
| 477 | 531 | private function build_master_sitemap( $max ) { |
| 478 | - $page = array(); | |
| 479 | - $image = array(); | |
| 480 | - $video = array(); | |
| 481 | 532 | if ( $this->logger ) { |
| 482 | 533 | $this->logger->report( '-- Building Master Sitemap.' ); |
| 483 | 534 | } |
| 484 | 535 | |
| 485 | - $buffer = new Jetpack_Sitemap_Buffer_Master( | |
| 536 | + $sitemap_types = array( | |
| 537 | + JP_PAGE_SITEMAP_TYPE, | |
| 538 | + JP_IMAGE_SITEMAP_TYPE, | |
| 539 | + JP_VIDEO_SITEMAP_TYPE, | |
| 540 | + ); | |
| 541 | + | |
| 542 | + $buffer = null; | |
| 543 | + | |
| 544 | + /** | |
| 545 | + * Whether the master sitemap lists each individual sitemap file directly. | |
| 546 | + * | |
| 547 | + * A sitemap index file may not list other sitemap index files, and Google | |
| 548 | + * Search Console reports the nested layout as "Nested indexing". Listing | |
| 549 | + * the files directly is what fixes that. | |
| 550 | + * | |
| 551 | + * Off by default so the flat layout can be rolled out a site at a time. | |
| 552 | + * The default is expected to flip once it has been verified in production, | |
| 553 | + * at which point this filter goes away. | |
| 554 | + * | |
| 555 | + * @module sitemaps | |
| 556 | + * | |
| 557 | + * @since 16.2 | |
| 558 | + * | |
| 559 | + * @param bool $flat_master_index Whether to list sitemap files directly. Default false. | |
| 560 | + */ | |
| 561 | + if ( apply_filters( 'jetpack_sitemap_flat_master_index', false ) ) { | |
| 562 | + $buffer = $this->build_flat_master_buffer( $sitemap_types, $max ); | |
| 563 | + } | |
| 564 | + | |
| 565 | + if ( null === $buffer || self::MASTER_OVERFLOW === $buffer ) { | |
| 566 | + /* | |
| 567 | + * Either the flat layout is off, or the files did not fit in one | |
| 568 | + * buffer. Nesting is invalid and Google flags it, but a complete | |
| 569 | + * invalid tree beats a valid one that drops URLs, and a sitemap | |
| 570 | + * index cannot be paginated to make the flat layout scale further. | |
| 571 | + */ | |
| 572 | + $buffer = $this->build_nested_master_buffer( $sitemap_types, $max ); | |
| 573 | + } | |
| 574 | + | |
| 575 | + if ( ! is_object( $buffer ) ) { | |
| 576 | + return; | |
| 577 | + } | |
| 578 | + | |
| 579 | + $this->librarian->store_sitemap_data( | |
| 580 | + 0, | |
| 581 | + JP_MASTER_SITEMAP_TYPE, | |
| 582 | + $buffer->contents(), | |
| 583 | + '' | |
| 584 | + ); | |
| 585 | + } | |
| 586 | + | |
| 587 | + /** | |
| 588 | + * Create the buffer a master sitemap is assembled in. | |
| 589 | + * | |
| 590 | + * Extracted as a protected seam so a test can hand back a buffer small | |
| 591 | + * enough to overflow without generating a million URLs. | |
| 592 | + * | |
| 593 | + * @access protected | |
| 594 | + * @since 16.2 | |
| 595 | + * | |
| 596 | + * @return Jetpack_Sitemap_Buffer|Jetpack_Sitemap_Buffer_XMLWriter|false The buffer, or false if one cannot be created. | |
| 597 | + */ | |
| 598 | + protected function create_master_buffer() { | |
| 599 | + return Jetpack_Sitemap_Buffer_Factory::create( | |
| 600 | + 'master', | |
| 486 | 601 | JP_SITEMAP_MAX_ITEMS, |
| 487 | 602 | JP_SITEMAP_MAX_BYTES |
| 488 | 603 | ); |
| 604 | + } | |
| 489 | 605 | |
| 490 | - if ( 0 < $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) { | |
| 491 | - if ( 1 === $max[ JP_PAGE_SITEMAP_TYPE ]['number'] ) { | |
| 492 | - $page['filename'] = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, 1 ); | |
| 493 | - $page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_TYPE ]['lastmod'] ); | |
| 494 | - } else { | |
| 495 | - $page['filename'] = jp_sitemap_filename( | |
| 496 | - JP_PAGE_SITEMAP_INDEX_TYPE, | |
| 497 | - $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['number'] | |
| 498 | - ); | |
| 499 | - $page['last_modified'] = jp_sitemap_datetime( $max[ JP_PAGE_SITEMAP_INDEX_TYPE ]['lastmod'] ); | |
| 606 | + /** | |
| 607 | + * Build a master sitemap buffer listing every individual sitemap file. | |
| 608 | + * | |
| 609 | + * The buffer's item and byte limits can both stop this short, and how soon | |
| 610 | + * the byte limit bites depends on how long this site's URLs are, so the | |
| 611 | + * partial buffer is discarded and MASTER_OVERFLOW returned rather than | |
| 612 | + * storing a master that omits sitemaps. Every type is still checked over | |
| 613 | + * once the buffer overflows, so a missing file is reported as | |
| 614 | + * MASTER_INCOMPLETE instead of sending the caller off to build a nested | |
| 615 | + * master out of the same broken state. | |
| 616 | + * | |
| 617 | + * @access private | |
| 618 | + * @since 16.2 | |
| 619 | + * | |
| 620 | + * @param array $sitemap_types The sitemap types to list, in order. | |
| 621 | + * @param array $max Array of sitemap types with max index and datetime. | |
| 622 | + * | |
| 623 | + * @return Jetpack_Sitemap_Buffer|Jetpack_Sitemap_Buffer_XMLWriter|string The buffer, or MASTER_OVERFLOW / MASTER_INCOMPLETE. | |
| 624 | + */ | |
| 625 | + private function build_flat_master_buffer( $sitemap_types, $max ) { | |
| 626 | + $buffer = $this->create_master_buffer(); | |
| 627 | + | |
| 628 | + if ( ! $buffer ) { | |
| 629 | + return self::MASTER_INCOMPLETE; | |
| 630 | + } | |
| 631 | + | |
| 632 | + $overflowed = false; | |
| 633 | + | |
| 634 | + foreach ( $sitemap_types as $sitemap_type ) { | |
| 635 | + $expected = $this->sitemap_count_of( $max, $sitemap_type ); | |
| 636 | + | |
| 637 | + if ( $expected < 1 ) { | |
| 638 | + continue; | |
| 500 | 639 | } |
| 501 | 640 | |
| 502 | - $buffer->append( | |
| 503 | - array( | |
| 504 | - 'sitemap' => array( | |
| 505 | - 'loc' => $this->finder->construct_sitemap_url( $page['filename'] ), | |
| 506 | - 'lastmod' => $page['last_modified'], | |
| 507 | - ), | |
| 508 | - ) | |
| 509 | - ); | |
| 641 | + $timestamps = $this->stored_sitemap_timestamps( $sitemap_type, $expected ); | |
| 642 | + | |
| 643 | + if ( false === $timestamps ) { | |
| 644 | + return self::MASTER_INCOMPLETE; | |
| 645 | + } | |
| 646 | + | |
| 647 | + if ( $overflowed ) { | |
| 648 | + continue; | |
| 649 | + } | |
| 650 | + | |
| 651 | + for ( $number = 1; $number <= $expected; $number++ ) { | |
| 652 | + $filename = jp_sitemap_filename( $sitemap_type, $number ); | |
| 653 | + | |
| 654 | + if ( ! $this->append_sitemap_to_master( $buffer, $filename, $timestamps[ $filename ] ) ) { | |
| 655 | + if ( $this->logger ) { | |
| 656 | + $this->logger->report( '-- Master Sitemap is full; falling back to nested indexes.' ); | |
| 657 | + } | |
| 658 | + | |
| 659 | + $overflowed = true; | |
| 660 | + break; | |
| 661 | + } | |
| 662 | + } | |
| 510 | 663 | } |
| 511 | 664 | |
| 512 | - if ( 0 < $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) { | |
| 513 | - if ( 1 === $max[ JP_IMAGE_SITEMAP_TYPE ]['number'] ) { | |
| 514 | - $image['filename'] = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, 1 ); | |
| 515 | - $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_TYPE ]['lastmod'] ); | |
| 516 | - } else { | |
| 517 | - $image['filename'] = jp_sitemap_filename( | |
| 518 | - JP_IMAGE_SITEMAP_INDEX_TYPE, | |
| 519 | - $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['number'] | |
| 520 | - ); | |
| 521 | - $image['last_modified'] = jp_sitemap_datetime( $max[ JP_IMAGE_SITEMAP_INDEX_TYPE ]['lastmod'] ); | |
| 665 | + return $overflowed ? self::MASTER_OVERFLOW : $buffer; | |
| 666 | + } | |
| 667 | + | |
| 668 | + /** | |
| 669 | + * Build a master sitemap buffer linking one file per sitemap type: the single | |
| 670 | + * sitemap when there is only one, otherwise that type's newest index file. | |
| 671 | + * | |
| 672 | + * Only used when the individual files do not all fit. The whole index chain | |
| 673 | + * has to be intact, because the newest index reaches the older ones only by | |
| 674 | + * linking back through them, so every index of a type is checked and not just | |
| 675 | + * the one the master names. | |
| 676 | + * | |
| 677 | + * @access private | |
| 678 | + * @since 16.2 | |
| 679 | + * | |
| 680 | + * @param array $sitemap_types The sitemap types to list, in order. | |
| 681 | + * @param array $max Array of sitemap types with max index and datetime. | |
| 682 | + * | |
| 683 | + * @return Jetpack_Sitemap_Buffer|Jetpack_Sitemap_Buffer_XMLWriter|string The buffer, or MASTER_INCOMPLETE. | |
| 684 | + */ | |
| 685 | + private function build_nested_master_buffer( $sitemap_types, $max ) { | |
| 686 | + $buffer = $this->create_master_buffer(); | |
| 687 | + | |
| 688 | + if ( ! $buffer ) { | |
| 689 | + return self::MASTER_INCOMPLETE; | |
| 690 | + } | |
| 691 | + | |
| 692 | + foreach ( $sitemap_types as $sitemap_type ) { | |
| 693 | + $expected = $this->sitemap_count_of( $max, $sitemap_type ); | |
| 694 | + | |
| 695 | + if ( $expected < 1 ) { | |
| 696 | + continue; | |
| 522 | 697 | } |
| 523 | 698 | |
| 524 | - $buffer->append( | |
| 525 | - array( | |
| 526 | - 'sitemap' => array( | |
| 527 | - 'loc' => $this->finder->construct_sitemap_url( $image['filename'] ), | |
| 528 | - 'lastmod' => $image['last_modified'], | |
| 529 | - ), | |
| 530 | - ) | |
| 531 | - ); | |
| 699 | + // The files reached through the index still have to be there. | |
| 700 | + $timestamps = $this->stored_sitemap_timestamps( $sitemap_type, $expected ); | |
| 701 | + | |
| 702 | + if ( false === $timestamps ) { | |
| 703 | + return self::MASTER_INCOMPLETE; | |
| 704 | + } | |
| 705 | + | |
| 706 | + $linked_type = $sitemap_type; | |
| 707 | + $linked_count = $expected; | |
| 708 | + | |
| 709 | + if ( 1 !== $expected ) { | |
| 710 | + // Only a type with a single sitemap has no index to link. | |
| 711 | + $linked_type = jp_sitemap_index_type_of( $sitemap_type ); | |
| 712 | + $linked_count = $this->sitemap_count_of( $max, $linked_type ); | |
| 713 | + $timestamps = $this->stored_sitemap_timestamps( $linked_type, $linked_count ); | |
| 714 | + | |
| 715 | + if ( $linked_count < 1 || false === $timestamps ) { | |
| 716 | + if ( $this->logger ) { | |
| 717 | + $this->logger->report( "-- No usable index for $sitemap_type; keeping the previous Master Sitemap." ); | |
| 718 | + } | |
| 719 | + | |
| 720 | + return self::MASTER_INCOMPLETE; | |
| 721 | + } | |
| 722 | + } | |
| 723 | + | |
| 724 | + $linked_name = jp_sitemap_filename( $linked_type, $linked_count ); | |
| 725 | + | |
| 726 | + if ( ! $this->append_sitemap_to_master( $buffer, $linked_name, $timestamps[ $linked_name ] ) ) { | |
| 727 | + if ( $this->logger ) { | |
| 728 | + $this->logger->report( "-- No room for $linked_name; keeping the previous Master Sitemap." ); | |
| 729 | + } | |
| 730 | + | |
| 731 | + return self::MASTER_INCOMPLETE; | |
| 732 | + } | |
| 532 | 733 | } |
| 533 | 734 | |
| 534 | - if ( 0 < $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) { | |
| 535 | - if ( 1 === $max[ JP_VIDEO_SITEMAP_TYPE ]['number'] ) { | |
| 536 | - $video['filename'] = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, 1 ); | |
| 537 | - $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_TYPE ]['lastmod'] ); | |
| 538 | - } else { | |
| 539 | - $video['filename'] = jp_sitemap_filename( | |
| 540 | - JP_VIDEO_SITEMAP_INDEX_TYPE, | |
| 541 | - $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['number'] | |
| 542 | - ); | |
| 543 | - $video['last_modified'] = jp_sitemap_datetime( $max[ JP_VIDEO_SITEMAP_INDEX_TYPE ]['lastmod'] ); | |
| 735 | + return $buffer; | |
| 736 | + } | |
| 737 | + | |
| 738 | + /** | |
| 739 | + * The number of sitemap files of a type the current generation cycle recorded. | |
| 740 | + * | |
| 741 | + * @access private | |
| 742 | + * @since 16.2 | |
| 743 | + * | |
| 744 | + * @param array $max Array of sitemap types with max index and datetime. | |
| 745 | + * @param string $type A sitemap or sitemap index type. | |
| 746 | + * | |
| 747 | + * @return int The count, or 0 if the type produced nothing. | |
| 748 | + */ | |
| 749 | + private function sitemap_count_of( $max, $type ) { | |
| 750 | + return isset( $max[ $type ]['number'] ) ? (int) $max[ $type ]['number'] : 0; | |
| 751 | + } | |
| 752 | + | |
| 753 | + /** | |
| 754 | + * Look up the timestamps of files 1..$count of a sitemap type, by filename. | |
| 755 | + * | |
| 756 | + * Filenames rather than row order, because a row rewritten by an interrupted | |
| 757 | + * cleanup no longer sorts where its number says it should. | |
| 758 | + * | |
| 759 | + * @access private | |
| 760 | + * @since 16.2 | |
| 761 | + * | |
| 762 | + * @param string $type A sitemap or sitemap index type. | |
| 763 | + * @param int $count How many files of that type to expect. | |
| 764 | + * | |
| 765 | + * @return array|false Map of filename to timestamp, or false if any is missing. | |
| 766 | + */ | |
| 767 | + private function stored_sitemap_timestamps( $type, $count ) { | |
| 768 | + $names = array(); | |
| 769 | + | |
| 770 | + for ( $number = 1; $number <= $count; $number++ ) { | |
| 771 | + $names[] = jp_sitemap_filename( $type, $number ); | |
| 772 | + } | |
| 773 | + | |
| 774 | + $timestamps = $this->librarian->query_sitemap_timestamps( $type, $names ); | |
| 775 | + | |
| 776 | + foreach ( $names as $name ) { | |
| 777 | + if ( ! isset( $timestamps[ $name ] ) ) { | |
| 778 | + if ( $this->logger ) { | |
| 779 | + $this->logger->report( "-- $name is missing; keeping the previous Master Sitemap." ); | |
| 780 | + } | |
| 781 | + | |
| 782 | + return false; | |
| 544 | 783 | } |
| 784 | + } | |
| 545 | 785 | |
| 546 | - $buffer->append( | |
| 547 | - array( | |
| 548 | - 'sitemap' => array( | |
| 549 | - 'loc' => $this->finder->construct_sitemap_url( $video['filename'] ), | |
| 550 | - 'lastmod' => $video['last_modified'], | |
| 551 | - ), | |
| 552 | - ) | |
| 553 | - ); | |
| 554 | - } | |
| 786 | + return $timestamps; | |
| 787 | + } | |
| 555 | 788 | |
| 556 | - $this->librarian->store_sitemap_data( | |
| 557 | - 0, | |
| 558 | - JP_MASTER_SITEMAP_TYPE, | |
| 559 | - $buffer->contents(), | |
| 560 | - '' | |
| 789 | + /** | |
| 790 | + * Append one <sitemap> entry to a master sitemap buffer. | |
| 791 | + * | |
| 792 | + * @access private | |
| 793 | + * @since 16.2 | |
| 794 | + * | |
| 795 | + * @param Jetpack_Sitemap_Buffer|Jetpack_Sitemap_Buffer_XMLWriter $buffer The master sitemap buffer. | |
| 796 | + * @param string $filename The sitemap filename to link. | |
| 797 | + * @param string $lastmod Its timestamp, in 'YYYY-MM-DD hh:mm:ss' format. | |
| 798 | + * | |
| 799 | + * @return bool Whether the entry fit. | |
| 800 | + */ | |
| 801 | + private function append_sitemap_to_master( $buffer, $filename, $lastmod ) { | |
| 802 | + return true === $buffer->append( | |
| 803 | + array( | |
| 804 | + 'sitemap' => array( | |
| 805 | + 'loc' => $this->finder->construct_sitemap_url( $filename ), | |
| 806 | + 'lastmod' => jp_sitemap_datetime( $lastmod ), | |
| 807 | + ), | |
| 808 | + ) | |
| 561 | 809 | ); |
| 562 | 810 | } |
| 563 | 811 | |
| 564 | 812 | /** |
| @@ -586,13 +834,18 @@ | ||
| 586 | 834 | $debug_name = jp_sitemap_filename( JP_PAGE_SITEMAP_TYPE, $number ); |
| 587 | 835 | $this->logger->report( "-- Building $debug_name" ); |
| 588 | 836 | } |
| 589 | 837 | |
| 590 | - $buffer = new Jetpack_Sitemap_Buffer_Page( | |
| 838 | + $buffer = Jetpack_Sitemap_Buffer_Factory::create( | |
| 839 | + 'page', | |
| 591 | 840 | JP_SITEMAP_MAX_ITEMS, |
| 592 | 841 | JP_SITEMAP_MAX_BYTES |
| 593 | 842 | ); |
| 594 | 843 | |
| 844 | + if ( ! $buffer ) { | |
| 845 | + return false; | |
| 846 | + } | |
| 847 | + | |
| 595 | 848 | // Add entry for the main page (only if we're at the first one) and it isn't already going to be included as a page. |
| 596 | 849 | if ( 1 === $number && 'page' !== get_option( 'show_on_front' ) ) { |
| 597 | 850 | $item_array = array( |
| 598 | 851 | 'url' => array( |
| @@ -639,9 +892,9 @@ | ||
| 639 | 892 | } |
| 640 | 893 | } |
| 641 | 894 | |
| 642 | 895 | // Handle other page sitemap URLs. |
| 643 | - if ( false === $any_posts_left || $last_post_id < 0 ) { | |
| 896 | + if ( ! $any_posts_left || $last_post_id < 0 ) { | |
| 644 | 897 | // Negative IDs are used to track URL indexes. |
| 645 | 898 | $last_post_id = min( 0, $last_post_id ); |
| 646 | 899 | $any_posts_left = true; // Reinitialize. |
| 647 | 900 | |
| @@ -685,8 +938,11 @@ | ||
| 685 | 938 | $item = array( 'xml' => compact( 'url' ) ); |
| 686 | 939 | |
| 687 | 940 | if ( true === $buffer->append( $item['xml'] ) ) { |
| 688 | 941 | $last_post_id = -$index; |
| 942 | + if ( isset( $url['lastmod'] ) ) { | |
| 943 | + $buffer->view_time( jp_sitemap_datetime( $url['lastmod'] ) ); | |
| 944 | + } | |
| 689 | 945 | } else { |
| 690 | 946 | break; |
| 691 | 947 | } |
| 692 | 948 | } |
| @@ -708,13 +964,15 @@ | ||
| 708 | 964 | * |
| 709 | 965 | * @param DOMDocument $doc Data tree for sitemap. |
| 710 | 966 | * @param string $last_modified Date of last modification. |
| 711 | 967 | */ |
| 712 | - $tree = apply_filters( // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable | |
| 713 | - 'jetpack_print_sitemap', | |
| 714 | - $buffer->get_document(), | |
| 715 | - $buffer->last_modified() | |
| 716 | - ); | |
| 968 | + if ( has_filter( 'jetpack_print_sitemap' ) ) { | |
| 969 | + apply_filters( | |
| 970 | + 'jetpack_print_sitemap', | |
| 971 | + $buffer->get_document(), | |
| 972 | + $buffer->last_modified() | |
| 973 | + ); | |
| 974 | + } | |
| 717 | 975 | |
| 718 | 976 | // Store the buffer as the content of a sitemap row. |
| 719 | 977 | $this->librarian->store_sitemap_data( |
| 720 | 978 | $number, |
| @@ -759,13 +1017,18 @@ | ||
| 759 | 1017 | $debug_name = jp_sitemap_filename( JP_IMAGE_SITEMAP_TYPE, $number ); |
| 760 | 1018 | $this->logger->report( "-- Building $debug_name" ); |
| 761 | 1019 | } |
| 762 | 1020 | |
| 763 | - $buffer = new Jetpack_Sitemap_Buffer_Image( | |
| 1021 | + $buffer = Jetpack_Sitemap_Buffer_Factory::create( | |
| 1022 | + 'image', | |
| 764 | 1023 | JP_SITEMAP_MAX_ITEMS, |
| 765 | 1024 | JP_SITEMAP_MAX_BYTES |
| 766 | 1025 | ); |
| 767 | 1026 | |
| 1027 | + if ( ! $buffer ) { | |
| 1028 | + return false; | |
| 1029 | + } | |
| 1030 | + | |
| 768 | 1031 | // Add as many items to the buffer as possible. |
| 769 | 1032 | while ( false === $buffer->is_full() ) { |
| 770 | 1033 | $posts = $this->librarian->query_images_after_id( |
| 771 | 1034 | $last_post_id, |
| @@ -838,13 +1101,18 @@ | ||
| 838 | 1101 | $debug_name = jp_sitemap_filename( JP_VIDEO_SITEMAP_TYPE, $number ); |
| 839 | 1102 | $this->logger->report( "-- Building $debug_name" ); |
| 840 | 1103 | } |
| 841 | 1104 | |
| 842 | - $buffer = new Jetpack_Sitemap_Buffer_Video( | |
| 1105 | + $buffer = Jetpack_Sitemap_Buffer_Factory::create( | |
| 1106 | + 'video', | |
| 843 | 1107 | JP_SITEMAP_MAX_ITEMS, |
| 844 | 1108 | JP_SITEMAP_MAX_BYTES |
| 845 | 1109 | ); |
| 846 | 1110 | |
| 1111 | + if ( ! $buffer ) { | |
| 1112 | + return false; | |
| 1113 | + } | |
| 1114 | + | |
| 847 | 1115 | // Add as many items to the buffer as possible. |
| 848 | 1116 | while ( false === $buffer->is_full() ) { |
| 849 | 1117 | $posts = $this->librarian->query_videos_after_id( |
| 850 | 1118 | $last_post_id, |
| @@ -925,13 +1193,17 @@ | ||
| 925 | 1193 | $index_debug_name = jp_sitemap_filename( $index_type, $number ); |
| 926 | 1194 | $this->logger->report( "-- Building $index_debug_name" ); |
| 927 | 1195 | } |
| 928 | 1196 | |
| 929 | - $buffer = new Jetpack_Sitemap_Buffer_Master( | |
| 1197 | + $buffer = Jetpack_Sitemap_Buffer_Factory::create( | |
| 1198 | + 'master', | |
| 930 | 1199 | JP_SITEMAP_MAX_ITEMS, |
| 931 | 1200 | JP_SITEMAP_MAX_BYTES, |
| 932 | 1201 | $datetime |
| 933 | 1202 | ); |
| 1203 | + if ( ! $buffer ) { | |
| 1204 | + return false; | |
| 1205 | + } | |
| 934 | 1206 | |
| 935 | 1207 | // Add pointer to the previous sitemap index (unless we're at the first one). |
| 936 | 1208 | if ( 1 !== $number ) { |
| 937 | 1209 | $i = $number - 1; |
| @@ -1055,8 +1327,18 @@ | ||
| 1055 | 1327 | * |
| 1056 | 1328 | * @return string The news sitemap xml. |
| 1057 | 1329 | */ |
| 1058 | 1330 | public function news_sitemap_xml() { |
| 1331 | + $buffer = Jetpack_Sitemap_Buffer_Factory::create( | |
| 1332 | + 'news', | |
| 1333 | + JP_SITEMAP_MAX_ITEMS, | |
| 1334 | + JP_SITEMAP_MAX_BYTES | |
| 1335 | + ); | |
| 1336 | + | |
| 1337 | + if ( ! $buffer ) { | |
| 1338 | + return ''; | |
| 1339 | + } | |
| 1340 | + | |
| 1059 | 1341 | $the_stored_news_sitemap = get_transient( 'jetpack_news_sitemap_xml' ); |
| 1060 | 1342 | |
| 1061 | 1343 | if ( false === $the_stored_news_sitemap ) { |
| 1062 | 1344 | |
| @@ -1077,20 +1359,18 @@ | ||
| 1077 | 1359 | 'jetpack_sitemap_news_sitemap_count', |
| 1078 | 1360 | JP_NEWS_SITEMAP_MAX_ITEMS |
| 1079 | 1361 | ); |
| 1080 | 1362 | |
| 1081 | - $buffer = new Jetpack_Sitemap_Buffer_News( | |
| 1082 | - min( $item_limit, JP_NEWS_SITEMAP_MAX_ITEMS ), | |
| 1083 | - JP_SITEMAP_MAX_BYTES | |
| 1084 | - ); | |
| 1363 | + $posts = $this->librarian->query_most_recent_posts( $item_limit ); | |
| 1364 | + if ( empty( $posts ) ) { | |
| 1365 | + $buffer->append( array( 'url' => array( 'loc' => home_url( '/' ) ) ) ); | |
| 1366 | + } else { | |
| 1367 | + foreach ( $posts as $post ) { | |
| 1368 | + $current_item = $this->post_to_news_sitemap_item( $post ); | |
| 1085 | 1369 | |
| 1086 | - $posts = $this->librarian->query_most_recent_posts( JP_NEWS_SITEMAP_MAX_ITEMS ); | |
| 1087 | - | |
| 1088 | - foreach ( $posts as $post ) { | |
| 1089 | - $current_item = $this->post_to_news_sitemap_item( $post ); | |
| 1090 | - | |
| 1091 | - if ( false === $buffer->append( $current_item['xml'] ) ) { | |
| 1092 | - break; | |
| 1370 | + if ( $current_item['xml'] !== null && false === $buffer->append( $current_item['xml'] ) ) { | |
| 1371 | + break; | |
| 1372 | + } | |
| 1093 | 1373 | } |
| 1094 | 1374 | } |
| 1095 | 1375 | |
| 1096 | 1376 | if ( $this->logger ) { |
| @@ -1115,9 +1395,9 @@ | ||
| 1115 | 1395 | * @link https://www.sitemaps.org/protocol.html#urldef |
| 1116 | 1396 | * @access private |
| 1117 | 1397 | * @since 4.8.0 |
| 1118 | 1398 | * |
| 1119 | - * @param WP_Post $post The post to be processed. | |
| 1399 | + * @param object $post The post to be processed. Similar to WP_Post, but without post_content and post_content_filtered. | |
| 1120 | 1400 | * |
| 1121 | 1401 | * @return array |
| 1122 | 1402 | * @type array $xml An XML fragment representing the post URL. |
| 1123 | 1403 | * @type string $last_modified Date post was last modified. |
| @@ -1132,8 +1412,9 @@ | ||
| 1132 | 1412 | * @since 3.9.0 |
| 1133 | 1413 | * |
| 1134 | 1414 | * @param bool $skip Current boolean. False by default, so no post is skipped. |
| 1135 | 1415 | * @param object $post Current post in the form of a $wpdb result object. Not WP_Post. |
| 1416 | + * Doesn't have all the properties of a WP_Post. | |
| 1136 | 1417 | */ |
| 1137 | 1418 | if ( true === apply_filters( 'jetpack_sitemap_skip_post', false, $post ) ) { |
| 1138 | 1419 | return array( |
| 1139 | 1420 | 'xml' => null, |
| @@ -1386,9 +1667,9 @@ | ||
| 1386 | 1667 | * |
| 1387 | 1668 | * @access private |
| 1388 | 1669 | * @since 4.8.0 |
| 1389 | 1670 | * |
| 1390 | - * @param WP_Post $post The post to be processed. | |
| 1671 | + * @param object $post The post to be processed. Similar to WP_Post, but without post_content and post_content_filtered. | |
| 1391 | 1672 | * |
| 1392 | 1673 | * @return string An XML fragment representing the post URL. |
| 1393 | 1674 | */ |
| 1394 | 1675 | private function post_to_news_sitemap_item( $post ) { |
| @@ -1402,10 +1683,11 @@ | ||
| 1402 | 1683 | * @module sitemaps |
| 1403 | 1684 | * |
| 1404 | 1685 | * @since 3.9.0 |
| 1405 | 1686 | * |
| 1406 | - * @param bool $skip Current boolean. False by default, so no post is skipped. | |
| 1407 | - * @param WP_POST $post Current post object. | |
| 1687 | + * @param bool $skip Current boolean. False by default, so no post is skipped. | |
| 1688 | + * @param object $post Current post in the form of a $wpdb result object. Not WP_Post. | |
| 1689 | + * Doesn't have all the properties of a WP_Post. | |
| 1408 | 1690 | */ |
| 1409 | 1691 | if ( apply_filters( 'jetpack_sitemap_news_skip_post', false, $post ) ) { |
| 1410 | 1692 | return array( |
| 1411 | 1693 | 'xml' => null, |