* * @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 ) ); } }