You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

317 lines
8.1 KiB
PHP

<?php
/**
* The sitemap provider for taxonomies.
*
* @since 0.9.0
* @package RankMath
* @subpackage RankMath\Sitemap
* @author Rank Math <support@rankmath.com>
*
* @copyright Copyright (C) 2008-2019, Yoast BV
* The following code is a derivative work of the code from the Yoast(https://github.com/Yoast/wordpress-seo/), which is licensed under GPL v3.
*/
namespace RankMath\Sitemap\Providers;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Sitemap\Router;
use RankMath\Sitemap\Sitemap;
use RankMath\Sitemap\Image_Parser;
defined( 'ABSPATH' ) || exit;
/**
* Taxonomy provider
*/
class Taxonomy implements Provider {
use Hooker;
/**
* Holds image parser instance.
*
* @var Image_Parser
*/
protected static $image_parser;
/**
* Check if provider supports given item type.
*
* @param string $type Type string to check for.
* @return boolean
*/
public function handles_type( $type ) {
if ( is_a( $type, 'WP_Taxonomy' ) ) {
$type = $type->name;
}
if (
empty( $type ) ||
false === taxonomy_exists( $type ) ||
false === Helper::is_taxonomy_viewable( $type ) ||
false === Helper::is_taxonomy_indexable( $type ) ||
in_array( $type, [ 'link_category', 'nav_menu', 'post_format' ], true )
) {
return false;
}
/**
* Filter decision if taxonomy is excluded from the XML sitemap.
*
* @param bool $exclude Default false.
* @param string $type Taxonomy name.
*/
return ! $this->do_filter( 'sitemap/exclude_taxonomy', false, $type );
}
/**
* Get set of sitemaps index link data.
*
* @param int $max_entries Entries per sitemap.
* @return array
*/
public function get_index_links( $max_entries ) {
$taxonomies = Helper::get_accessible_taxonomies();
$taxonomies = array_filter( $taxonomies, [ $this, 'handles_type' ] );
if ( empty( $taxonomies ) ) {
return [];
}
// Retrieve all the taxonomies and their terms so we can do a proper count on them.
/**
* Filter the setting of excluding empty terms from the XML sitemap.
*
* @param boolean $exclude Defaults to true.
* @param array $taxonomy_names Array of names for the taxonomies being processed.
*/
$hide_empty = $this->do_filter( 'sitemap/exclude_empty_terms', true, $taxonomies );
$all_taxonomies = [];
foreach ( $taxonomies as $taxonomy_name => $object ) {
$all_taxonomies[ $taxonomy_name ] = get_terms(
$taxonomy_name,
[
'hide_empty' => $hide_empty,
'fields' => 'ids',
'orderby' => 'name',
'meta_query' => [
'relation' => 'OR',
[
'key' => 'rank_math_robots',
'value' => 'noindex',
'compare' => 'NOT LIKE',
],
[
'key' => 'rank_math_robots',
'compare' => 'NOT EXISTS',
],
],
]
);
}
$index = [];
foreach ( $all_taxonomies as $tax_name => $terms ) {
if ( is_wp_error( $terms ) ) {
continue;
}
$max_pages = 1;
$total_count = empty( $terms ) ? 1 : count( $terms );
if ( $total_count > $max_entries ) {
$max_pages = (int) ceil( $total_count / $max_entries );
}
$tax = $taxonomies[ $tax_name ];
if ( ! is_array( $tax->object_type ) || count( $tax->object_type ) === 0 ) {
continue;
}
$last_modified_gmt = Sitemap::get_last_modified_gmt( $tax->object_type );
for ( $page_counter = 0; $page_counter < $max_pages; $page_counter++ ) {
$current_page = ( $max_pages > 1 ) ? ( $page_counter + 1 ) : '';
$terms_page = array_splice( $terms, 0, $max_entries );
if ( ! $terms_page ) {
continue;
}
$query = new \WP_Query(
[
'post_type' => $tax->object_type,
'tax_query' => [
[
'taxonomy' => $tax_name,
'terms' => $terms_page,
],
],
'orderby' => 'modified',
'order' => 'DESC',
'posts_per_page' => 1,
]
);
$item = $this->do_filter(
'sitemap/index/entry',
[
'loc' => Router::get_base_url( $tax_name . '-sitemap' . $current_page . '.xml' ),
'lastmod' => $query->have_posts() ? $query->posts[0]->post_modified_gmt : $last_modified_gmt,
],
'term',
$tax_name,
);
if ( ! $item ) {
continue;
}
$index[] = $item;
}
}
return $index;
}
/**
* Get set of sitemap link data.
*
* @param string $type Sitemap type.
* @param int $max_entries Entries per sitemap.
* @param int $current_page Current page of the sitemap.
* @return array
*/
public function get_sitemap_links( $type, $max_entries, $current_page ) {
$links = [];
$taxonomy = get_taxonomy( $type );
$terms = $this->get_terms( $taxonomy, $max_entries, $current_page );
Sitemap::maybe_redirect( count( $this->get_terms( $taxonomy, 0, $current_page ) ), $max_entries );
foreach ( $terms as $term ) {
$url = [];
if ( ! Sitemap::is_object_indexable( $term, 'term' ) ) {
continue;
}
$link = $this->get_term_link( $term );
if ( ! $link ) {
continue;
}
$url['loc'] = $link;
$url['mod'] = $this->get_lastmod( $term );
$url['images'] = ! is_null( $this->get_image_parser() ) ? $this->get_image_parser()->get_term_images( $term ) : [];
/** This filter is documented at inc/sitemaps/class-post-type-sitemap-provider.php */
$url = $this->do_filter( 'sitemap/entry', $url, 'term', $term );
if ( ! empty( $url ) ) {
$links[] = $url;
}
}
return $links;
}
/**
* Get the Image Parser.
*
* @return Image_Parser
*/
protected function get_image_parser() {
if ( class_exists( 'RankMath\Sitemap\Image_Parser' ) && ! isset( self::$image_parser ) ) {
self::$image_parser = new Image_Parser();
}
return self::$image_parser;
}
/**
* Get terms for taxonomy.
*
* @param object $taxonomy Taxonomy name.
* @param int $max_entries Entries per sitemap.
* @param int $current_page Current page of the sitemap.
* @return false|array
*/
private function get_terms( $taxonomy, $max_entries, $current_page ) {
$offset = $current_page > 1 ? ( ( $current_page - 1 ) * $max_entries ) : 0;
$hide_empty = ! Helper::get_settings( 'sitemap.tax_' . $taxonomy->name . '_include_empty' );
// Getting terms.
$terms = get_terms(
[
'taxonomy' => $taxonomy->name,
'orderby' => 'term_order',
'hide_empty' => $hide_empty,
'offset' => $offset,
'number' => $max_entries,
'exclude' => wp_parse_id_list( Helper::get_settings( 'sitemap.exclude_terms' ) ),
/*
* Limits aren't included in queries when hierarchical is set to true (by default).
*
* @link: https://github.com/WordPress/WordPress/blob/5.3/wp-includes/class-wp-term-query.php#L558-L567
*/
'hierarchical' => false,
'update_term_meta_cache' => false,
]
);
if ( is_wp_error( $terms ) || empty( $terms ) ) {
return [];
}
return $terms;
}
/**
* Get term link.
*
* @param WP_Term $term Term object.
* @return string
*/
private function get_term_link( $term ) {
$url = get_term_link( $term, $term->taxonomy );
$canonical = Helper::get_term_meta( 'canonical_url', $term, $term->taxonomy );
if ( $canonical && $canonical !== $url ) {
/*
* Let's assume that if a canonical is set for this term and it's different from
* the URL of this term, that page is either already in the XML sitemap OR is on
* an external site, either way, we shouldn't include it here.
*/
return false;
}
return $url;
}
/**
* Get last modified date of post by term.
*
* @param WP_Term $term Term object.
* @return string
*/
public function get_lastmod( $term ) {
global $wpdb;
return $wpdb->get_var(
$wpdb->prepare(
"
SELECT MAX(p.post_modified_gmt) AS lastmod
FROM $wpdb->posts AS p
INNER JOIN $wpdb->term_relationships AS term_rel
ON term_rel.object_id = p.ID
INNER JOIN $wpdb->term_taxonomy AS term_tax
ON term_tax.term_taxonomy_id = term_rel.term_taxonomy_id
AND term_tax.taxonomy = %s
AND term_tax.term_id = %d
WHERE p.post_status IN ('publish', 'inherit')
AND p.post_password = ''
",
$term->taxonomy,
$term->term_id
)
);
}
}