<?php
/**
* Core plugin functionality
*
* @package simple-google-news-sitemap
*/
namespace SimpleGoogleNewsSitemap;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Core plugin functionality.
*/
class Core {
/**
* News sitemap slug.
*
* @var string
*/
private $sitemap_slug = 'news-sitemap';
/**
* Setup hooks.
*/
public function init() {
add_filter( 'template_include', [ $this, 'load_sitemap_template' ] );
add_filter( 'posts_pre_query', [ $this, 'disable_main_query_for_sitemap_xml' ], 10, 2 );
add_filter( 'robots_txt', [ $this, 'add_sitemap_robots_txt' ] );
add_action( 'init', [ $this, 'create_rewrites' ] );
add_action( 'publish_post', [ $this, 'purge_sitemap_data_on_update' ], 1000, 3 );
add_action( 'transition_post_status', [ $this, 'purge_sitemap_data_on_status_change' ], 1000, 3 );
add_action( 'publish_post', [ $this, 'ping_google' ], 2000 );
add_action( 'delete_post', [ $this, 'purge_sitemap_data_on_delete' ], 1000, 2 );
}
/**
* Render sitemap.
*
* @param string $template Template file to use.
*
* @return string
*/
public function load_sitemap_template( string $template ): string {
if ( 'true' === get_query_var( $this->sitemap_slug ) ) {
return dirname( __DIR__ ) . '/templates/google-news-sitemap.php';
}
return $template;
}
/**
* Add rewrite rules/tags.
*
* @return void
*/
public function create_rewrites() {
add_rewrite_tag( '%' . $this->sitemap_slug . '%', 'true' );
add_rewrite_rule( sprintf( '^%s.xml$', $this->sitemap_slug ), sprintf( 'index.php?%s=true', $this->sitemap_slug ), 'top' );
add_action( 'redirect_canonical', [ $this, 'disable_canonical_redirects_for_sitemap_xml' ], 10, 2 );
}
/**
* Remove rewrite rules/tags
*
* @return void
*/
public function remove_rewrites() {
remove_rewrite_tag( '%' . $this->sitemap_slug . '%', 'true' );
global $wp_rewrite;
unset( $wp_rewrite->extra_rules_top[ sprintf( '^%s.xml$', $this->sitemap_slug ) ] );
}
/**
* Disable Main Query when rendering sitemaps.
*
* @param array|null $posts array of post data or null.
* @param \WP_Query $query The WP_Query instance.
*
* @return array|null
*/
public function disable_main_query_for_sitemap_xml( $posts, \WP_Query $query ) {
if ( $query->is_main_query() && ! empty( $query->query_vars[ $this->sitemap_slug ] ) ) {
$posts = [];
}
return $posts;
}
/**
* Disable canonical redirects for the sitemap files.
*
* @param string $redirect_url URL to redirect to.
* @param string $requested_url Originally requested url.
*
* @return string URL to redirect
*/
public function disable_canonical_redirects_for_sitemap_xml( string $redirect_url, string $requested_url ): string {
if ( preg_match( sprintf( '/%s.xml/i', $this->sitemap_slug ), $requested_url ) ) {
return $requested_url;
}
return $redirect_url;
}
/**
* Add the sitemap URL to robots.txt file.
*
* @param string $output Robots.txt output.
*
* @return string
*/
public function add_sitemap_robots_txt( string $output ): string {
$url = home_url( sprintf( '/%s.xml', $this->sitemap_slug ) );
if ( ! get_option( 'permalink_structure' ) ) {
$url = add_query_arg( $this->sitemap_slug, 'true', home_url( '/' ) );
}
$output .= "\n" . esc_html__( 'Sitemap', 'simple-google-news-sitemap' ) . ": {$url}\n";
return $output;
}
/**
* Purges sitemap data when the post is updated.
*
* @param int $post_id Post ID.
* @param \WP_Post $post Post object.
* @param string $old_status Old post status.
*
* @return boolean
*/
public function purge_sitemap_data_on_update( int $post_id, \WP_Post $post, string $old_status ): bool {
$sitemap = new Sitemap();
// Don't purge cache for non-supported post types.
if ( ! in_array( $post->post_type, $sitemap->get_post_types(), true ) ) {
return false;
}
// Purge cache on updates.
if ( 'publish' === $old_status && $old_status === $post->post_status ) {
return CacheUtils::delete_cache();
}
return false;
}
/**
* Purges sitemap data when the post is published.
*
* @param string $new_status New post status.
* @param string $old_status Old post status.
* @param \WP_Post $post Post object.
*
* @return boolean
*/
public function purge_sitemap_data_on_status_change( string $new_status, string $old_status, \WP_Post $post ): bool {
$sitemap = new Sitemap();
// Don't purge cache for non-supported post types.
if ( ! in_array( $post->post_type, $sitemap->get_post_types(), true ) ) {
return false;
}
// Post date & range converted to timestamp.
$post_publish_date = strtotime( $post->post_date );
$range = strtotime( $sitemap->get_range() );
// Post statuses we clear the cache on.
$post_statuses = [
'future',
'private',
'pending',
'draft',
'trash',
'auto-draft',
];
/**
* Filter the post statuses we look for to determine if cache needs cleared.
*
* @since 1.0.0
*
* @hook simple_google_news_sitemap_post_statuses_to_clear
* @param {array} $post_statuses Post statuses we clear cache on.
* @returns {array} Filtered post statuses.
*/
$post_statuses = apply_filters( 'simple_google_news_sitemap_post_statuses_to_clear', $post_statuses );
/*
* POST status is updated or changed to trash / future / pending / private / draft.
* If the publish date falls within the range, we flush cache.
*/
if (
'publish' === $old_status && in_array( $new_status, $post_statuses, true )
|| in_array( $old_status, $post_statuses, true ) && 'publish' === $new_status
) {
if ( $post_publish_date > $range ) {
return CacheUtils::delete_cache();
}
}
return false;
}
/**
* Ping Google News after a news post is published.
*
* @return boolean
*/
public function ping_google(): bool {
/**
* Decide whether to ping Google when the sitemap changes.
*
* @since 1.0.0
*
* @hook simple_google_news_sitemap_ping
* @param {boolean} $should_ping Should we ping Google? Default true.
* @returns {boolean} Should we ping Google?
*/
if ( false === apply_filters( 'simple_google_news_sitemap_ping', true ) ) {
return false;
}
if ( '0' === get_option( 'blog_public' ) ) {
return false;
}
// Sitemap URL.
$url = site_url( sprintf( '/%s.xml', $this->sitemap_slug ) );
// Ping Google.
$ping = wp_remote_get( sprintf( 'https://www.google.com/ping?sitemap=%s', rawurlencode( esc_url_raw( $url ) ) ), [ 'blocking' => false ] );
if ( ! is_array( $ping ) || is_wp_error( $ping ) ) {
return false;
}
// Assume a successful ping.
// Returning "true" when the "wp_remote_get()" method doesn't return a "WP_Error" object for non-blocking requests.
// When a "WP_Error" object is not returned, there is no way to determine if the request was successful or not.
// Provided that the URL is correct and reachable, it should be OK to return "true" in that case.
return true;
}
/**
* Purges sitemap data on post_delete.
*
* This one is for the cases when the post is deleted directly via CLI and does
* not go to trash.
*
* @param int $post_id Post ID.
* @param \WP_Post $post Post object.
*
* @return boolean
*/
public function purge_sitemap_data_on_delete( int $post_id, \WP_Post $post ): bool {
$sitemap = new Sitemap();
// Don't purge cache for non-supported post types.
if ( ! in_array( $post->post_type, $sitemap->get_post_types(), true ) ) {
return false;
}
// Post date & range converted to timestamp.
$post_publish_date = strtotime( $post->post_date );
$range = strtotime( $sitemap->get_range() );
// If the publish date is within range from current time, we purge the cache.
if ( $post_publish_date > $range ) {
return CacheUtils::delete_cache();
}
// For rest, we do nothing.
return false;
}
}