PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.4
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.4
28.6 28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 All 130 releases
wordpress-seo / inc / sitemaps / class-taxonomy-sitemap-provider.php

class-taxonomy-sitemap-provider.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.4, at inc/sitemaps/class-taxonomy-sitemap-provider.php

352 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\XML_Sitemaps
6 */
7
8 /**
9 * Sitemap provider for author archives.
10 */
11 class WPSEO_Taxonomy_Sitemap_Provider implements WPSEO_Sitemap_Provider {
12
13 /**
14 * Holds image parser instance.
15 *
16 * @var WPSEO_Sitemap_Image_Parser
17 */
18 protected static $image_parser;
19
20 /**
21 * Determines whether images should be included in the XML sitemap.
22 *
23 * @var bool
24 */
25 private $include_images;
26
27 /**
28 * Set up object properties for data reuse.
29 */
30 public function __construct() {
31 /**
32 * Filter - Allows excluding images from the XML sitemap.
33 *
34 * @param bool $include True to include, false to exclude.
35 */
36 $this->include_images = apply_filters( 'wpseo_xml_sitemap_include_images', true );
37 }
38
39 /**
40 * Check if provider supports given item type.
41 *
42 * @param string $type Type string to check for.
43 *
44 * @return bool
45 */
46 public function handles_type( $type ) {
47
48 $taxonomy = get_taxonomy( $type );
49
50 if ( $taxonomy === false || ! $this->is_valid_taxonomy( $taxonomy->name ) || ! $taxonomy->public ) {
51 return false;
52 }
53
54 return true;
55 }
56
57 /**
58 * Retrieves the links for the sitemap.
59 *
60 * @param int $max_entries Entries per sitemap.
61 *
62 * @return array
63 */
64 public function get_index_links( $max_entries ) {
65
66 $taxonomies = get_taxonomies( [ 'public' => true ], 'objects' );
67
68 if ( empty( $taxonomies ) ) {
69 return [];
70 }
71
72 $taxonomy_names = array_filter( array_keys( $taxonomies ), [ $this, 'is_valid_taxonomy' ] );
73 $taxonomies = array_intersect_key( $taxonomies, array_flip( $taxonomy_names ) );
74
75 // Retrieve all the taxonomies and their terms so we can do a proper count on them.
76
77 /**
78 * Filter the setting of excluding empty terms from the XML sitemap.
79 *
80 * @param bool $exclude Defaults to true.
81 * @param array $taxonomy_names Array of names for the taxonomies being processed.
82 */
83 $hide_empty = apply_filters( 'wpseo_sitemap_exclude_empty_terms', true, $taxonomy_names );
84
85 $all_taxonomies = [];
86
87 foreach ( $taxonomy_names as $taxonomy_name ) {
88 /**
89 * Filter the setting of excluding empty terms from the XML sitemap for a specific taxonomy.
90 *
91 * @param bool $exclude Defaults to the sitewide setting.
92 * @param string $taxonomy_name The name of the taxonomy being processed.
93 */
94 $hide_empty_tax = apply_filters( 'wpseo_sitemap_exclude_empty_terms_taxonomy', $hide_empty, $taxonomy_name );
95
96 $term_args = [
97 'taxonomy' => $taxonomy_name,
98 'hide_empty' => $hide_empty_tax,
99 'fields' => 'ids',
100 ];
101 $taxonomy_terms = get_terms( $term_args );
102
103 if ( count( $taxonomy_terms ) > 0 ) {
104 $all_taxonomies[ $taxonomy_name ] = $taxonomy_terms;
105 }
106 }
107
108 $index = [];
109
110 foreach ( $taxonomies as $tax_name => $tax ) {
111
112 if ( ! isset( $all_taxonomies[ $tax_name ] ) ) { // No eligible terms found.
113 continue;
114 }
115
116 $total_count = ( isset( $all_taxonomies[ $tax_name ] ) ) ? count( $all_taxonomies[ $tax_name ] ) : 1;
117 $max_pages = 1;
118
119 if ( $total_count > $max_entries ) {
120 $max_pages = (int) ceil( $total_count / $max_entries );
121 }
122
123 $last_modified_gmt = WPSEO_Sitemaps::get_last_modified_gmt( $tax->object_type );
124
125 for ( $page_counter = 0; $page_counter < $max_pages; $page_counter++ ) {
126
127 $current_page = ( $page_counter === 0 ) ? '' : ( $page_counter + 1 );
128
129 if ( ! is_array( $tax->object_type ) || count( $tax->object_type ) === 0 ) {
130 continue;
131 }
132
133 $terms = array_splice( $all_taxonomies[ $tax_name ], 0, $max_entries );
134
135 if ( ! $terms ) {
136 continue;
137 }
138
139 $args = [
140 'post_type' => $tax->object_type,
141 'tax_query' => [
142 [
143 'taxonomy' => $tax_name,
144 'terms' => $terms,
145 ],
146 ],
147 'orderby' => 'modified',
148 'order' => 'DESC',
149 'posts_per_page' => 1,
150 ];
151 $query = new WP_Query( $args );
152
153 if ( $query->have_posts() ) {
154 $date = $query->posts[0]->post_modified_gmt;
155 }
156 else {
157 $date = $last_modified_gmt;
158 }
159
160 $index[] = [
161 'loc' => WPSEO_Sitemaps_Router::get_base_url( $tax_name . '-sitemap' . $current_page . '.xml' ),
162 'lastmod' => $date,
163 ];
164 }
165 }
166
167 return $index;
168 }
169
170 /**
171 * Get set of sitemap link data.
172 *
173 * @param string $type Sitemap type.
174 * @param int $max_entries Entries per sitemap.
175 * @param int $current_page Current page of the sitemap.
176 *
177 * @return array
178 *
179 * @throws OutOfBoundsException When an invalid page is requested.
180 */
181 public function get_sitemap_links( $type, $max_entries, $current_page ) {
182 global $wpdb;
183
184 $links = [];
185 if ( ! $this->handles_type( $type ) ) {
186 return $links;
187 }
188
189 $taxonomy = get_taxonomy( $type );
190
191 $steps = $max_entries;
192 $offset = ( $current_page > 1 ) ? ( ( $current_page - 1 ) * $max_entries ) : 0;
193
194 /** This filter is documented in inc/sitemaps/class-taxonomy-sitemap-provider.php */
195 $hide_empty = apply_filters( 'wpseo_sitemap_exclude_empty_terms', true, [ $taxonomy->name ] );
196 /** This filter is documented in inc/sitemaps/class-taxonomy-sitemap-provider.php */
197 $hide_empty_tax = apply_filters( 'wpseo_sitemap_exclude_empty_terms_taxonomy', $hide_empty, $taxonomy->name );
198 $terms = get_terms(
199 [
200 'taxonomy' => $taxonomy->name,
201 'hide_empty' => $hide_empty_tax,
202 'update_term_meta_cache' => false,
203 'offset' => $offset,
204 'number' => $steps,
205 ],
206 );
207
208 // If there are no terms fetched for this range, we are on an invalid page.
209 if ( empty( $terms ) ) {
210 throw new OutOfBoundsException( 'Invalid sitemap page requested' );
211 }
212
213 $post_statuses = array_map( 'esc_sql', WPSEO_Sitemaps::get_post_statuses() );
214
215 $replacements = array_merge(
216 [
217 'post_modified_gmt',
218 $wpdb->posts,
219 $wpdb->term_relationships,
220 'object_id',
221 'ID',
222 $wpdb->term_taxonomy,
223 'term_taxonomy_id',
224 'term_taxonomy_id',
225 'taxonomy',
226 'term_id',
227 'post_status',
228 ],
229 $post_statuses,
230 [ 'post_password' ],
231 );
232
233 /**
234 * Filter: 'wpseo_exclude_from_sitemap_by_term_ids' - Allow excluding terms by ID.
235 *
236 * @param array $terms_to_exclude The terms to exclude.
237 */
238 $terms_to_exclude = apply_filters( 'wpseo_exclude_from_sitemap_by_term_ids', [] );
239
240 foreach ( $terms as $term ) {
241
242 if ( in_array( $term->term_id, $terms_to_exclude, true ) ) {
243 continue;
244 }
245
246 $url = [];
247
248 $tax_noindex = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'noindex' );
249
250 if ( $tax_noindex === 'noindex' ) {
251 continue;
252 }
253
254 $canonical = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'canonical' );
255 $url['loc'] = get_term_link( $term, $term->taxonomy );
256
257 if ( is_string( $canonical ) && $canonical !== '' && $canonical !== $url['loc'] ) {
258 continue;
259 }
260
261 $current_replacements = $replacements;
262 array_splice( $current_replacements, 9, 0, $term->taxonomy );
263 array_splice( $current_replacements, 11, 0, $term->term_id );
264
265 //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need to use a direct query here.
266 //phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
267 $url['mod'] = $wpdb->get_var(
268 //phpcs:disable WordPress.DB.PreparedSQLPlaceholders -- %i placeholder is still not recognized.
269 $wpdb->prepare(
270 '
271 SELECT MAX(p.%i) AS lastmod
272 FROM %i AS p
273 INNER JOIN %i AS term_rel
274 ON term_rel.%i = p.%i
275 INNER JOIN %i AS term_tax
276 ON term_tax.%i = term_rel.%i
277 AND term_tax.%i = %s
278 AND term_tax.%i = %d
279 WHERE p.%i IN (' . implode( ', ', array_fill( 0, count( $post_statuses ), '%s' ) ) . ")
280 AND p.%i = ''
281 ",
282 $current_replacements,
283 ),
284 );
285
286 if ( $this->include_images ) {
287 $url['images'] = $this->get_image_parser()->get_term_images( $term );
288 }
289
290 // Deprecated, kept for backwards data compat. R.
291 $url['chf'] = 'daily';
292 $url['pri'] = 1;
293
294 /** This filter is documented at inc/sitemaps/class-post-type-sitemap-provider.php */
295 $url = apply_filters( 'wpseo_sitemap_entry', $url, 'term', $term );
296
297 if ( ! empty( $url ) ) {
298 $links[] = $url;
299 }
300 }
301
302 return $links;
303 }
304
305 /**
306 * Check if taxonomy by name is valid to appear in sitemaps.
307 *
308 * @param string $taxonomy_name Taxonomy name to check.
309 *
310 * @return bool
311 */
312 public function is_valid_taxonomy( $taxonomy_name ) {
313
314 if ( WPSEO_Options::get( "noindex-tax-{$taxonomy_name}" ) === true ) {
315 return false;
316 }
317
318 if ( in_array( $taxonomy_name, [ 'link_category', 'nav_menu', 'wp_pattern_category' ], true ) ) {
319 return false;
320 }
321
322 if ( $taxonomy_name === 'post_format' && WPSEO_Options::get( 'disable-post_format', false ) ) {
323 return false;
324 }
325
326 /**
327 * Filter to exclude the taxonomy from the XML sitemap.
328 *
329 * @param bool $exclude Defaults to false.
330 * @param string $taxonomy_name Name of the taxonomy to exclude..
331 */
332 if ( apply_filters( 'wpseo_sitemap_exclude_taxonomy', false, $taxonomy_name ) ) {
333 return false;
334 }
335
336 return true;
337 }
338
339 /**
340 * Get the Image Parser.
341 *
342 * @return WPSEO_Sitemap_Image_Parser
343 */
344 protected function get_image_parser() {
345 if ( ! isset( self::$image_parser ) ) {
346 self::$image_parser = new WPSEO_Sitemap_Image_Parser();
347 }
348
349 return self::$image_parser;
350 }
351 }
352