PluginProbe
SEO Engine – Smart SEO with AI, Schema & Redirection for WordPress / 0.9.4
SEO Engine – Smart SEO with AI, Schema & Redirection for WordPress v0.9.4
0.9.4 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.2 0.8.1 0.7.9 0.8.0 0.7.7 0.7.8 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.5 All 88 releases
seo-engine / classes / sitemap.php

sitemap.php in SEO Engine – Smart SEO with AI, Schema & Redirection for WordPress 0.9.4, at classes/sitemap.php

438 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 class Meow_MWSEO_Sitemap extends WP_Sitemaps_Provider
3 {
4 private $core = null;
5
6 public function __construct( $core )
7 {
8 $this->core = $core;
9 $this->init();
10 }
11
12 function init( )
13 {
14 $disabled = $this->core->get_option( 'disable_wp_sitemap', false );
15 $custom = $this->core->get_option( 'sitemap_custom', false );
16 if ( $disabled ) {
17 add_filter( 'wp_sitemaps_enabled', '__return_false' );
18
19 // Make our own sitemap if the WP sitemap is disabled
20 if ( $custom ) {
21 // Generate the sitemap on a new post save
22 add_action( 'wp_after_insert_post', array( $this, 'create_sitemap_callback' ), 10, 3 );
23
24 // Generate the sitemap on post deletion
25 // add_action( 'delete_post', array( $this, 'create_sitemap_callback' ), 10, 3 );
26
27
28
29 add_filter( 'robots_txt', array( $this, 'add_sitemap_to_robots' ), 10, 1 );
30 }
31
32
33 return;
34 }
35
36 // Excluding Providers
37 $excluded_providers = [
38 $this->core->get_option( 'sitemap_exclude_users_provider', false ) ? 'users' : null,
39 $this->core->get_option( 'sitemap_exclude_posts_provider', false ) ? 'posts' : null,
40 $this->core->get_option( 'sitemap_exclude_taxonomies_provider', false ) ? 'taxonomies' : null,
41 ];
42
43 add_filter(
44 'wp_sitemaps_add_provider',
45 function ( $provider, $name ) use ( $excluded_providers ) {
46 if ( in_array( $name, $excluded_providers ) ) {
47 return false;
48 }
49
50 return $provider;
51 },
52 10,
53 2
54 );
55
56 // Exclude Post Types
57 $excluded_post_types = $this->core->get_option('sitemap_excluded_post_types', []);
58 add_filter(
59 'wp_sitemaps_post_types',
60 function ($post_types) use ($excluded_post_types) {
61
62 foreach ($excluded_post_types as $excluded_post_type) {
63 if (isset($post_types[$excluded_post_type])) {
64 unset($post_types[$excluded_post_type]);
65 }
66 }
67
68 return $post_types;
69 },
70 10,
71 1
72 );
73
74 //Exclude Taxonomies
75 $excluded_taxonomies = $this->core->get_option( 'sitemap_excluded_taxonomies', [] );
76 add_filter(
77 'wp_sitemaps_taxonomies',
78 function ( $taxonomies ) use ( $excluded_taxonomies ) {
79
80 foreach( $excluded_taxonomies as $excluded_taxonomy ) {
81 if ( isset( $taxonomies[$excluded_taxonomy] ) ) {
82 unset( $taxonomies[$excluded_taxonomy] );
83 }
84 }
85
86 return $taxonomies;
87 }
88 );
89
90
91 //Exclude specific posts
92 $excluded_posts = $this->core->get_option( 'sitemap_excluded_post_ids', [] );
93 try {
94 $excluded_posts = array_map( 'intval', $excluded_posts );
95 } catch ( Exception $e ) {
96 $excluded_posts = [];
97 $this->core->log( '❌ ( Sitemap ) Error parsing excluded post ids.' );
98 }
99 add_filter(
100 'wp_sitemaps_posts_query_args',
101 function ( $args, $post_type ) use ( $excluded_posts ) {
102 // if ( $post_type !== 'post' ) {
103 // return $args;
104 // }
105 $args['post__not_in'] = isset( $args['post__not_in'] ) ? $args['post__not_in'] : array( );
106 $args['post__not_in'] = array_merge( $args['post__not_in'], $excluded_posts );
107
108 return $args;
109 },
110 10,
111 2
112 );
113 }
114
115 public function get_url_list( $page, $post_type = null )
116 {
117 $urls = [];
118 $posts = get_posts( [
119 'post_type' => $post_type,
120 'posts_per_page' => $this->core->get_option( 'sitemap_max_urls', 100 ),
121 'paged' => $page,
122 'fields' => 'ids',
123 ] );
124
125 foreach ( $posts as $post_id ) {
126 $urls[] = get_permalink( $post_id );
127 }
128
129 return $urls;
130 }
131
132 public function get_max_num_pages( $object_subtype = '' )
133 {
134 $post_type = $object_subtype;
135 $args = [
136 'post_type' => $post_type,
137 'posts_per_page' => $this->core->get_option( 'sitemap_post_max_pages', 100 ),
138 'fields' => 'ids',
139 ];
140 // A sitemap covers every language, and unlike get_url_list() below this is a
141 // WP_Query, so Bogo would narrow the count down to the site locale.
142 $args = $this->core->apply_language_filter( $args, 'all' );
143
144 $query = new WP_Query( $args );
145 return $query->max_num_pages;
146 }
147
148 public function add_sitemap_to_robots( $output )
149 {
150 // Remove existing Sitemap line
151 $output = preg_replace("/Sitemap: .*/", "", $output);
152
153 // Append new Sitemap line with correct WordPress URL
154 $output .= "Sitemap: " . $this->core->get_option( 'sitemap_path' ) . "\n";
155 return $output;
156 }
157
158
159 public function create_sitemap_callback( $post, $update, $before_post )
160 {
161 $post = is_numeric( $post ) ? get_post( $post ) : $post;
162 if ( wp_is_post_revision( $post ) || $post->post_status != 'publish' ) {
163 return;
164 }
165
166 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
167
168 if ( get_transient( 'mwseo_sitemap_generating' ) ) {
169 return;
170 }
171
172 set_transient( 'mwseo_sitemap_generating', true, 5 );
173
174 $this->core->log( "🗺️ Sitemap was generated automatically ( Post ID: {$post->ID}, Post Title: {$post->post_title} )" );
175
176 $this->create_sitemap();
177 }
178
179 private $style = null;
180 public function create_sitemap() {
181 // Using the same settings as the WP core sitemap
182 $excluded_post_types = $this->core->get_option( 'sitemap_excluded_post_types', [] );
183 $excluded_posts = $this->core->get_option( 'sitemap_excluded_post_ids', [] );
184 $excluded_taxonomies = $this->core->get_option( 'sitemap_excluded_taxonomies', [] );
185
186 $sitemap_style = $this->core->get_option( 'sitemap_style', 'default' );
187 $sitemap_style = $sitemap_style == 'default' ? '' : '-' . $sitemap_style;
188 $this->style = $sitemap_style;
189
190 // Convert excluded posts to integers (and log errors if any)
191 try {
192 $excluded_posts = array_map( 'intval', $excluded_posts );
193 } catch ( Exception $e ) {
194 $excluded_posts = [];
195 $this->core->log( '❌ (Sitemap) Error parsing excluded post ids.' );
196 }
197
198 // * We want the same structure as the WP default sitemap ( With sub-sitemaps )
199 // We’ll store each sub-sitemap file path + URL here
200 $sitemaps = [];
201
202 // Remove the ones excluded by the user in the plugin options
203 $public_post_types = get_post_types( [ 'public' => true ], 'names' );
204 foreach ( $excluded_post_types as $excluded_type ) {
205 if ( isset( $public_post_types[ $excluded_type ] ) ) {
206 unset( $public_post_types[ $excluded_type ] );
207 } elseif ( ( $key = array_search( $excluded_type, $public_post_types ) ) !== false ) {
208 unset( $public_post_types[ $key ] );
209 }
210 }
211
212 // Then create sub-sitemap for each post type
213 foreach ( $public_post_types as $post_type ) {
214 $sub_sitemap_data = $this->create_sitemap_for_post_type( $post_type, $excluded_posts );
215 // If successfully created, add it to index
216 if ( $sub_sitemap_data && ! empty( $sub_sitemap_data['filename'] ) ) {
217 $sitemaps[] = [
218 'loc' => site_url( '/' . $sub_sitemap_data['filename'] ),
219 ];
220 }
221 }
222
223 // We do the same for taxonomies
224 $public_taxonomies = get_taxonomies( [ 'public' => true ], 'names' );
225 foreach ( $excluded_taxonomies as $excluded_tax ) {
226 if ( isset( $public_taxonomies[ $excluded_tax ] ) ) {
227 unset( $public_taxonomies[ $excluded_tax ] );
228 }
229 }
230
231 // Create sub-sitemap for each taxonomy
232 foreach ( $public_taxonomies as $tax_name ) {
233 $sub_sitemap_data = $this->create_sitemap_for_taxonomy( $tax_name );
234 if ( $sub_sitemap_data && ! empty( $sub_sitemap_data['filename'] ) ) {
235 $sitemaps[] = [
236 'loc' => site_url( '/' . $sub_sitemap_data['filename'] ),
237 ];
238 }
239 }
240
241 // We do the same for users
242 $exclude_users_provider = $this->core->get_option( 'sitemap_exclude_users_provider', false );
243 if ( ! $exclude_users_provider ) {
244 $sub_sitemap_data = $this->create_sitemap_for_users();
245 if ( $sub_sitemap_data && ! empty( $sub_sitemap_data['filename'] ) ) {
246 $sitemaps[] = [
247 'loc' => site_url( '/' . $sub_sitemap_data['filename'] ),
248 ];
249 }
250 }
251
252 // * Now we create the main sitemap index file
253 $index_xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
254 $index_xml .= '<?xml-stylesheet type="text/xsl" href="' . MWSEO_URL . 'classes/sitemap-style' . $this->style . '.xsl"?>' . "\n";
255 $index_xml .= '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
256
257 foreach ( $sitemaps as $item ) {
258 $index_xml .= " <sitemap>\n";
259 $index_xml .= " <loc>" . esc_url( $item['loc'] ) . "</loc>\n";
260 $index_xml .= " </sitemap>\n";
261 }
262
263 $index_xml .= '</sitemapindex>' . "\n";
264
265 // TODO: Maybe we should have an option to choose the filename
266 $filename = 'wp-sitemap.xml';
267
268 if ( ! function_exists( 'get_home_path' ) ) {
269 require_once ABSPATH . 'wp-admin/includes/file.php';
270 }
271 $home_path = get_home_path();
272
273 if ( ! is_writable( $home_path ) && ! empty( $_SERVER['DOCUMENT_ROOT'] ) ) {
274 $home_path = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR;
275 }
276
277 $filepath = $home_path . $filename;
278 $sitepath = site_url( '/' . $filename );
279
280 if ( !empty( $index_xml ) ) {
281 $content = $index_xml;
282
283 $f = fopen( $filepath, 'w+' );
284 fwrite( $f, $content );
285 fclose( $f );
286 }
287
288 $realpath = realpath( $filepath );
289
290 $this->core->update_option( 'sitemap_path', $sitepath );
291
292 return [
293 'filename' => $filename,
294 'filepath' => $realpath,
295 'url' => $sitepath,
296 ];
297 }
298
299 private function create_sitemap_for_post_type( $post_type, $excluded_posts = [] ) {
300
301 $exlcude_posts_provider = $this->core->get_option( 'sitemap_exclude_posts_provider', false );
302 if ( $exlcude_posts_provider ) return false;
303
304 $posts = get_posts([
305 'post_type' => $post_type,
306 'post__not_in' => $excluded_posts,
307 'orderby' => 'modified',
308 'order' => 'DESC',
309 'posts_per_page' => $this->core->get_option( 'sitemap_max_urls', 100 ),
310 ]);
311
312 // If no posts found, maybe skip generating a file
313 if ( empty( $posts ) ) {
314 return false;
315 }
316
317 // Build sub-sitemap XML
318 $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
319 $xml .= '<?xml-stylesheet type="text/xsl" href="' . MWSEO_URL . 'classes/sitemap-style' . $this->style . '.xsl"?>' . "\n";
320 $xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
321
322 foreach ( $posts as $post ) {
323 setup_postdata( $post );
324 $post_lastmod = $post->post_modified;
325 $lastmod_iso8601 = mysql2date( 'c', $post_lastmod, false );
326
327 $xml .= " <url>\n";
328 $xml .= " <loc>" . esc_url( get_permalink( $post->ID ) ) . "</loc>\n";
329 $xml .= " <lastmod>" . esc_html( $lastmod_iso8601 ) . "</lastmod>\n";
330 $xml .= " <changefreq>daily</changefreq>\n";
331 $xml .= " <priority>1.0</priority>\n";
332 $xml .= " </url>\n";
333 }
334
335 $xml .= "</urlset>\n";
336
337 // Generate a filename: e.g. wp-sitemap-posts-{$post_type}-1.xml
338 $filename = "wp-sitemap-posts-{$post_type}-1.xml";
339 $filepath = ABSPATH . $filename;
340 file_put_contents( $filepath, $xml );
341
342 wp_reset_postdata();
343
344 return [
345 'filename' => $filename,
346 'filepath' => realpath( $filepath ),
347 ];
348 }
349
350 private function create_sitemap_for_taxonomy( $taxonomy ) {
351
352 $exclude_taxonomies_provider = $this->core->get_option( 'sitemap_exclude_taxonomies_provider', false );
353 if ( $exclude_taxonomies_provider ) return false;
354
355 // Get all public terms in the taxonomy
356 $terms = get_terms([
357 'taxonomy' => $taxonomy,
358 'hide_empty' => false,
359 ]);
360
361 if ( empty( $terms ) || is_wp_error( $terms ) ) {
362 return false;
363 }
364
365 $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
366 $xml .= '<?xml-stylesheet type="text/xsl" href="' . MWSEO_URL . 'classes/sitemap-style' . $this->style . '.xsl"?>' . "\n";
367 $xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
368
369 foreach ( $terms as $term ) {
370 // The link to a term archive page
371 $term_link = get_term_link( $term );
372 if ( is_wp_error( $term_link ) ) {
373 continue;
374 }
375
376 $xml .= " <url>\n";
377 $xml .= " <loc>" . esc_url( $term_link ) . "</loc>\n";
378 $xml .= " <changefreq>daily</changefreq>\n";
379 $xml .= " <priority>0.8</priority>\n";
380 $xml .= " </url>\n";
381 }
382
383 $xml .= "</urlset>\n";
384
385 $filename = "wp-sitemap-taxonomies-{$taxonomy}-1.xml";
386 $filepath = ABSPATH . $filename;
387 file_put_contents( $filepath, $xml );
388
389 return [
390 'filename' => $filename,
391 'filepath' => realpath( $filepath ),
392 ];
393 }
394
395 private function create_sitemap_for_users() {
396 $exclude_users_provider = $this->core->get_option( 'sitemap_exclude_users_provider', false );
397 if ( $exclude_users_provider ) return false;
398
399 // TODO: maybe filter out non-authors or specific roles with an option?
400 // WordPress seems like it only includes users who have authored posts
401 $users = get_users([
402 'who' => 'authors',
403 ]);
404
405 if ( empty( $users ) ) {
406 return false;
407 }
408
409 $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
410 $xml .= '<?xml-stylesheet type="text/xsl" href="' . MWSEO_URL . 'classes/sitemap-style' . $this->style . '.xsl"?>' . "\n";
411 $xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
412
413 foreach ( $users as $user ) {
414 // Author archive link
415 $author_link = get_author_posts_url( $user->ID );
416 $xml .= " <url>\n";
417 $xml .= " <loc>" . esc_url( $author_link ) . "</loc>\n";
418 $xml .= " <changefreq>daily</changefreq>\n";
419 $xml .= " <priority>0.5</priority>\n";
420 $xml .= " </url>\n";
421 }
422
423 $xml .= "</urlset>\n";
424
425 $filename = "wp-sitemap-users-1.xml";
426 $filepath = ABSPATH . $filename;
427 file_put_contents( $filepath, $xml );
428
429 return [
430 'filename' => $filename,
431 'filepath' => realpath( $filepath ),
432 ];
433 }
434
435 }
436
437
438 ?>