first
This commit is contained in:
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap provider for author archives.
|
||||
*/
|
||||
class WPSEO_Author_Sitemap_Provider implements WPSEO_Sitemap_Provider {
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handles_type( $type ) {
|
||||
// If the author archives have been disabled, we don't do anything.
|
||||
if ( WPSEO_Options::get( 'disable-author', false ) || WPSEO_Options::get( 'noindex-author-wpseo', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $type === 'author';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the links for the sitemap index.
|
||||
*
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries ) {
|
||||
|
||||
if ( ! $this->handles_type( 'author' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// @todo Consider doing this less often / when necessary. R.
|
||||
$this->update_user_meta();
|
||||
|
||||
$has_exclude_filter = has_filter( 'wpseo_sitemap_exclude_author' );
|
||||
|
||||
$query_arguments = [];
|
||||
|
||||
if ( ! $has_exclude_filter ) { // We only need full users if legacy filter(s) hooked to exclusion logic. R.
|
||||
$query_arguments['fields'] = 'ID';
|
||||
}
|
||||
|
||||
$users = $this->get_users( $query_arguments );
|
||||
|
||||
if ( $has_exclude_filter ) {
|
||||
$users = $this->exclude_users( $users );
|
||||
$users = wp_list_pluck( $users, 'ID' );
|
||||
}
|
||||
|
||||
if ( empty( $users ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$index = [];
|
||||
$user_pages = array_chunk( $users, $max_entries );
|
||||
|
||||
foreach ( $user_pages as $page_counter => $users_page ) {
|
||||
|
||||
$current_page = ( $page_counter === 0 ) ? '' : ( $page_counter + 1 );
|
||||
|
||||
$user_id = array_shift( $users_page ); // Time descending, first user on page is most recently updated.
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
$index[] = [
|
||||
'loc' => WPSEO_Sitemaps_Router::get_base_url( 'author-sitemap' . $current_page . '.xml' ),
|
||||
'lastmod' => ( $user->_yoast_wpseo_profile_updated ) ? YoastSEO()->helpers->date->format_timestamp( $user->_yoast_wpseo_profile_updated ) : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve users, taking account of all necessary exclusions.
|
||||
*
|
||||
* @param array $arguments Arguments to add.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_users( $arguments = [] ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$defaults = [
|
||||
'capability' => [ 'edit_posts' ],
|
||||
'meta_key' => '_yoast_wpseo_profile_updated',
|
||||
'orderby' => 'meta_value_num',
|
||||
'order' => 'DESC',
|
||||
'meta_query' => [
|
||||
'relation' => 'AND',
|
||||
[
|
||||
'key' => $wpdb->get_blog_prefix() . 'user_level',
|
||||
'value' => '0',
|
||||
'compare' => '!=',
|
||||
],
|
||||
[
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => 'wpseo_noindex_author',
|
||||
'value' => 'on',
|
||||
'compare' => '!=',
|
||||
],
|
||||
[
|
||||
'key' => 'wpseo_noindex_author',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ( WPSEO_Options::get( 'noindex-author-noposts-wpseo', true ) ) {
|
||||
unset( $defaults['capability'] ); // Otherwise it cancels out next argument.
|
||||
$defaults['has_published_posts'] = YoastSEO()->helpers->author_archive->get_author_archive_post_types();
|
||||
}
|
||||
|
||||
return get_users( array_merge( $defaults, $arguments ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @throws OutOfBoundsException When an invalid page is requested.
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page ) {
|
||||
|
||||
$links = [];
|
||||
|
||||
if ( ! $this->handles_type( 'author' ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$user_criteria = [
|
||||
'offset' => ( ( $current_page - 1 ) * $max_entries ),
|
||||
'number' => $max_entries,
|
||||
];
|
||||
|
||||
$users = $this->get_users( $user_criteria );
|
||||
|
||||
// Throw an exception when there are no users in the sitemap.
|
||||
if ( count( $users ) === 0 ) {
|
||||
throw new OutOfBoundsException( 'Invalid sitemap page requested' );
|
||||
}
|
||||
|
||||
$users = $this->exclude_users( $users );
|
||||
if ( empty( $users ) ) {
|
||||
$users = [];
|
||||
}
|
||||
|
||||
$time = time();
|
||||
|
||||
foreach ( $users as $user ) {
|
||||
|
||||
$author_link = get_author_posts_url( $user->ID );
|
||||
|
||||
if ( empty( $author_link ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mod = $time;
|
||||
|
||||
if ( isset( $user->_yoast_wpseo_profile_updated ) ) {
|
||||
$mod = $user->_yoast_wpseo_profile_updated;
|
||||
}
|
||||
|
||||
$url = [
|
||||
'loc' => $author_link,
|
||||
'mod' => date( DATE_W3C, $mod ),
|
||||
|
||||
// Deprecated, kept for backwards data compat. R.
|
||||
'chf' => 'daily',
|
||||
'pri' => 1,
|
||||
];
|
||||
|
||||
/** This filter is documented at inc/sitemaps/class-post-type-sitemap-provider.php */
|
||||
$url = apply_filters( 'wpseo_sitemap_entry', $url, 'user', $user );
|
||||
|
||||
if ( ! empty( $url ) ) {
|
||||
$links[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update any users that don't have last profile update timestamp.
|
||||
*
|
||||
* @return int Count of users updated.
|
||||
*/
|
||||
protected function update_user_meta() {
|
||||
|
||||
$user_criteria = [
|
||||
'capability' => [ 'edit_posts' ],
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => '_yoast_wpseo_profile_updated',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$users = get_users( $user_criteria );
|
||||
|
||||
$time = time();
|
||||
|
||||
foreach ( $users as $user ) {
|
||||
update_user_meta( $user->ID, '_yoast_wpseo_profile_updated', $time );
|
||||
}
|
||||
|
||||
return count( $users );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap legacy filter to deduplicate calls.
|
||||
*
|
||||
* @param array $users Array of user objects to filter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function exclude_users( $users ) {
|
||||
|
||||
/**
|
||||
* Filter the authors, included in XML sitemap.
|
||||
*
|
||||
* @param array $users Array of user objects to filter.
|
||||
*/
|
||||
return apply_filters( 'wpseo_sitemap_exclude_author', $users );
|
||||
}
|
||||
}
|
@ -0,0 +1,667 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Models\SEO_Links;
|
||||
|
||||
/**
|
||||
* Sitemap provider for author archives.
|
||||
*/
|
||||
class WPSEO_Post_Type_Sitemap_Provider implements WPSEO_Sitemap_Provider {
|
||||
|
||||
/**
|
||||
* Holds image parser instance.
|
||||
*
|
||||
* @var WPSEO_Sitemap_Image_Parser
|
||||
*/
|
||||
protected static $image_parser;
|
||||
|
||||
/**
|
||||
* Holds the parsed home url.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $parsed_home_url;
|
||||
|
||||
/**
|
||||
* Determines whether images should be included in the XML sitemap.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $include_images;
|
||||
|
||||
/**
|
||||
* Set up object properties for data reuse.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'save_post', [ $this, 'save_post' ] );
|
||||
|
||||
/**
|
||||
* Filter - Allows excluding images from the XML sitemap.
|
||||
*
|
||||
* @param bool $include True to include, false to exclude.
|
||||
*/
|
||||
$this->include_images = apply_filters( 'wpseo_xml_sitemap_include_images', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Image Parser.
|
||||
*
|
||||
* @return WPSEO_Sitemap_Image_Parser
|
||||
*/
|
||||
protected function get_image_parser() {
|
||||
if ( ! isset( self::$image_parser ) ) {
|
||||
self::$image_parser = new WPSEO_Sitemap_Image_Parser();
|
||||
}
|
||||
|
||||
return self::$image_parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parsed home url.
|
||||
*
|
||||
* @return array The home url, as parsed by wp_parse_url.
|
||||
*/
|
||||
protected function get_parsed_home_url() {
|
||||
if ( ! isset( self::$parsed_home_url ) ) {
|
||||
self::$parsed_home_url = wp_parse_url( home_url() );
|
||||
}
|
||||
|
||||
return self::$parsed_home_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handles_type( $type ) {
|
||||
|
||||
return post_type_exists( $type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sitemap links.
|
||||
*
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries ) {
|
||||
global $wpdb;
|
||||
|
||||
$post_types = WPSEO_Post_Type::get_accessible_post_types();
|
||||
$post_types = array_filter( $post_types, [ $this, 'is_valid_post_type' ] );
|
||||
$last_modified_times = WPSEO_Sitemaps::get_last_modified_gmt( $post_types, true );
|
||||
$index = [];
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
|
||||
$total_count = $this->get_post_type_count( $post_type );
|
||||
|
||||
if ( $total_count === 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$max_pages = 1;
|
||||
if ( $total_count > $max_entries ) {
|
||||
$max_pages = (int) ceil( $total_count / $max_entries );
|
||||
}
|
||||
|
||||
$all_dates = [];
|
||||
|
||||
if ( $max_pages > 1 ) {
|
||||
$post_statuses = array_map( 'esc_sql', WPSEO_Sitemaps::get_post_statuses( $post_type ) );
|
||||
|
||||
$sql = "
|
||||
SELECT post_modified_gmt
|
||||
FROM ( SELECT @rownum:=0 ) init
|
||||
JOIN {$wpdb->posts} USE INDEX( type_status_date )
|
||||
WHERE post_status IN ('" . implode( "','", $post_statuses ) . "')
|
||||
AND post_type = %s
|
||||
AND ( @rownum:=@rownum+1 ) %% %d = 0
|
||||
ORDER BY post_modified_gmt ASC
|
||||
";
|
||||
|
||||
$all_dates = $wpdb->get_col( $wpdb->prepare( $sql, $post_type, $max_entries ) );
|
||||
}
|
||||
|
||||
for ( $page_counter = 0; $page_counter < $max_pages; $page_counter++ ) {
|
||||
|
||||
$current_page = ( $page_counter === 0 ) ? '' : ( $page_counter + 1 );
|
||||
$date = false;
|
||||
|
||||
if ( empty( $current_page ) || $current_page === $max_pages ) {
|
||||
|
||||
if ( ! empty( $last_modified_times[ $post_type ] ) ) {
|
||||
$date = $last_modified_times[ $post_type ];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$date = $all_dates[ $page_counter ];
|
||||
}
|
||||
|
||||
$index[] = [
|
||||
'loc' => WPSEO_Sitemaps_Router::get_base_url( $post_type . '-sitemap' . $current_page . '.xml' ),
|
||||
'lastmod' => $date,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @throws OutOfBoundsException When an invalid page is requested.
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page ) {
|
||||
|
||||
$links = [];
|
||||
$post_type = $type;
|
||||
|
||||
if ( ! $this->is_valid_post_type( $post_type ) ) {
|
||||
throw new OutOfBoundsException( 'Invalid sitemap page requested' );
|
||||
}
|
||||
|
||||
$steps = min( 100, $max_entries );
|
||||
$offset = ( $current_page > 1 ) ? ( ( $current_page - 1 ) * $max_entries ) : 0;
|
||||
$total = ( $offset + $max_entries );
|
||||
|
||||
$post_type_entries = $this->get_post_type_count( $post_type );
|
||||
|
||||
if ( $total > $post_type_entries ) {
|
||||
$total = $post_type_entries;
|
||||
}
|
||||
|
||||
if ( $current_page === 1 ) {
|
||||
$links = array_merge( $links, $this->get_first_links( $post_type ) );
|
||||
}
|
||||
|
||||
// If total post type count is lower than the offset, an invalid page is requested.
|
||||
if ( $post_type_entries < $offset ) {
|
||||
throw new OutOfBoundsException( 'Invalid sitemap page requested' );
|
||||
}
|
||||
|
||||
if ( $post_type_entries === 0 ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$posts_to_exclude = $this->get_excluded_posts( $type );
|
||||
|
||||
while ( $total > $offset ) {
|
||||
|
||||
$posts = $this->get_posts( $post_type, $steps, $offset );
|
||||
|
||||
$offset += $steps;
|
||||
|
||||
if ( empty( $posts ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
|
||||
if ( in_array( $post->ID, $posts_to_exclude, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( WPSEO_Meta::get_value( 'meta-robots-noindex', $post->ID ) === '1' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $this->get_url( $post );
|
||||
|
||||
if ( ! isset( $url['loc'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter URL entry before it gets added to the sitemap.
|
||||
*
|
||||
* @param array $url Array of URL parts.
|
||||
* @param string $type URL type.
|
||||
* @param object $post Data object for the URL.
|
||||
*/
|
||||
$url = apply_filters( 'wpseo_sitemap_entry', $url, 'post', $post );
|
||||
|
||||
if ( ! empty( $url ) ) {
|
||||
$links[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $post, $url );
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for relevant post type before invalidation.
|
||||
*
|
||||
* @param int $post_id Post ID to possibly invalidate for.
|
||||
*/
|
||||
public function save_post( $post_id ) {
|
||||
|
||||
if ( $this->is_valid_post_type( get_post_type( $post_id ) ) ) {
|
||||
WPSEO_Sitemaps_Cache::invalidate_post( $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if post type should be present in sitemaps.
|
||||
*
|
||||
* @param string $post_type Post type string to check for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_post_type( $post_type ) {
|
||||
if ( ! WPSEO_Post_Type::is_post_type_accessible( $post_type ) || ! WPSEO_Post_Type::is_post_type_indexable( $post_type ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter decision if post type is excluded from the XML sitemap.
|
||||
*
|
||||
* @param bool $exclude Default false.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
if ( apply_filters( 'wpseo_sitemap_exclude_post_type', false, $post_type ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list with the excluded post ids.
|
||||
*
|
||||
* @param string $post_type Post type.
|
||||
*
|
||||
* @return array Array with post ids to exclude.
|
||||
*/
|
||||
protected function get_excluded_posts( $post_type ) {
|
||||
$excluded_posts_ids = [];
|
||||
|
||||
$page_on_front_id = ( $post_type === 'page' ) ? (int) get_option( 'page_on_front' ) : 0;
|
||||
if ( $page_on_front_id > 0 ) {
|
||||
$excluded_posts_ids[] = $page_on_front_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_exclude_from_sitemap_by_post_ids' - Allow extending and modifying the posts to exclude.
|
||||
*
|
||||
* @api array $posts_to_exclude The posts to exclude.
|
||||
*/
|
||||
$excluded_posts_ids = apply_filters( 'wpseo_exclude_from_sitemap_by_post_ids', $excluded_posts_ids );
|
||||
if ( ! is_array( $excluded_posts_ids ) ) {
|
||||
$excluded_posts_ids = [];
|
||||
}
|
||||
|
||||
$excluded_posts_ids = array_map( 'intval', $excluded_posts_ids );
|
||||
|
||||
$page_for_posts_id = ( $post_type === 'page' ) ? (int) get_option( 'page_for_posts' ) : 0;
|
||||
if ( $page_for_posts_id > 0 ) {
|
||||
$excluded_posts_ids[] = $page_for_posts_id;
|
||||
}
|
||||
|
||||
return array_unique( $excluded_posts_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of posts for post type.
|
||||
*
|
||||
* @param string $post_type Post type to retrieve count for.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_post_type_count( $post_type ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
/**
|
||||
* Filter JOIN query part for type count of post type.
|
||||
*
|
||||
* @param string $join SQL part, defaults to empty string.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
$join_filter = apply_filters( 'wpseo_typecount_join', '', $post_type );
|
||||
|
||||
/**
|
||||
* Filter WHERE query part for type count of post type.
|
||||
*
|
||||
* @param string $where SQL part, defaults to empty string.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
$where_filter = apply_filters( 'wpseo_typecount_where', '', $post_type );
|
||||
|
||||
$where = $this->get_sql_where_clause( $post_type );
|
||||
|
||||
$sql = "
|
||||
SELECT COUNT({$wpdb->posts}.ID)
|
||||
FROM {$wpdb->posts}
|
||||
{$join_filter}
|
||||
{$where}
|
||||
{$where_filter}
|
||||
";
|
||||
|
||||
return (int) $wpdb->get_var( $sql );
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces set of links to prepend at start of first sitemap page.
|
||||
*
|
||||
* @param string $post_type Post type to produce links for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_first_links( $post_type ) {
|
||||
|
||||
$links = [];
|
||||
$archive_url = false;
|
||||
|
||||
if ( $post_type === 'page' ) {
|
||||
|
||||
$page_on_front_id = (int) get_option( 'page_on_front' );
|
||||
if ( $page_on_front_id > 0 ) {
|
||||
$front_page = $this->get_url(
|
||||
get_post( $page_on_front_id )
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $front_page ) ) {
|
||||
$front_page = [
|
||||
'loc' => YoastSEO()->helpers->url->home(),
|
||||
];
|
||||
}
|
||||
|
||||
// Deprecated, kept for backwards data compat. R.
|
||||
$front_page['chf'] = 'daily';
|
||||
$front_page['pri'] = 1;
|
||||
|
||||
$links[] = $front_page;
|
||||
}
|
||||
elseif ( $post_type !== 'page' ) {
|
||||
/**
|
||||
* Filter the URL Yoast SEO uses in the XML sitemap for this post type archive.
|
||||
*
|
||||
* @param string $archive_url The URL of this archive
|
||||
* @param string $post_type The post type this archive is for.
|
||||
*/
|
||||
$archive_url = apply_filters(
|
||||
'wpseo_sitemap_post_type_archive_link',
|
||||
$this->get_post_type_archive_link( $post_type ),
|
||||
$post_type
|
||||
);
|
||||
}
|
||||
|
||||
if ( $archive_url ) {
|
||||
|
||||
$links[] = [
|
||||
'loc' => $archive_url,
|
||||
'mod' => WPSEO_Sitemaps::get_last_modified_gmt( $post_type ),
|
||||
|
||||
// Deprecated, kept for backwards data compat. R.
|
||||
'chf' => 'daily',
|
||||
'pri' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the first post type links.
|
||||
*
|
||||
* @param array $links The first post type links.
|
||||
* @param string $post_type The post type this archive is for.
|
||||
*/
|
||||
return apply_filters( 'wpseo_sitemap_post_type_first_links', $links, $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL for a post type archive.
|
||||
*
|
||||
* @since 5.3
|
||||
*
|
||||
* @param string $post_type Post type.
|
||||
*
|
||||
* @return string|bool URL or false if it should be excluded.
|
||||
*/
|
||||
protected function get_post_type_archive_link( $post_type ) {
|
||||
|
||||
$pt_archive_page_id = -1;
|
||||
|
||||
if ( $post_type === 'post' ) {
|
||||
|
||||
if ( get_option( 'show_on_front' ) === 'posts' ) {
|
||||
return YoastSEO()->helpers->url->home();
|
||||
}
|
||||
|
||||
$pt_archive_page_id = (int) get_option( 'page_for_posts' );
|
||||
|
||||
// Post archive should be excluded if posts page isn't set.
|
||||
if ( $pt_archive_page_id <= 0 ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $this->is_post_type_archive_indexable( $post_type, $pt_archive_page_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_post_type_archive_link( $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a post type archive is indexable.
|
||||
*
|
||||
* @since 11.5
|
||||
*
|
||||
* @param string $post_type Post type.
|
||||
* @param int $archive_page_id The page id.
|
||||
*
|
||||
* @return bool True when post type archive is indexable.
|
||||
*/
|
||||
protected function is_post_type_archive_indexable( $post_type, $archive_page_id = -1 ) {
|
||||
|
||||
if ( WPSEO_Options::get( 'noindex-ptarchive-' . $post_type, false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the page which is dedicated to this post type archive.
|
||||
*
|
||||
* @since 9.3
|
||||
*
|
||||
* @param string $archive_page_id The post_id of the page.
|
||||
* @param string $post_type The post type this archive is for.
|
||||
*/
|
||||
$archive_page_id = (int) apply_filters( 'wpseo_sitemap_page_for_post_type_archive', $archive_page_id, $post_type );
|
||||
|
||||
if ( $archive_page_id > 0 && WPSEO_Meta::get_value( 'meta-robots-noindex', $archive_page_id ) === '1' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve set of posts with optimized query routine.
|
||||
*
|
||||
* @param string $post_type Post type to retrieve.
|
||||
* @param int $count Count of posts to retrieve.
|
||||
* @param int $offset Starting offset.
|
||||
*
|
||||
* @return object[]
|
||||
*/
|
||||
protected function get_posts( $post_type, $count, $offset ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
static $filters = [];
|
||||
|
||||
if ( ! isset( $filters[ $post_type ] ) ) {
|
||||
// Make sure you're wpdb->preparing everything you throw into this!!
|
||||
$filters[ $post_type ] = [
|
||||
/**
|
||||
* Filter JOIN query part for the post type.
|
||||
*
|
||||
* @param string $join SQL part, defaults to false.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
'join' => apply_filters( 'wpseo_posts_join', false, $post_type ),
|
||||
|
||||
/**
|
||||
* Filter WHERE query part for the post type.
|
||||
*
|
||||
* @param string $where SQL part, defaults to false.
|
||||
* @param string $post_type Post type name.
|
||||
*/
|
||||
'where' => apply_filters( 'wpseo_posts_where', false, $post_type ),
|
||||
];
|
||||
}
|
||||
|
||||
$join_filter = $filters[ $post_type ]['join'];
|
||||
$where_filter = $filters[ $post_type ]['where'];
|
||||
$where = $this->get_sql_where_clause( $post_type );
|
||||
|
||||
/*
|
||||
* Optimized query per this thread:
|
||||
* {@link http://wordpress.org/support/topic/plugin-wordpress-seo-by-yoast-performance-suggestion}.
|
||||
* Also see {@link http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/}.
|
||||
*/
|
||||
$sql = "
|
||||
SELECT l.ID, post_title, post_content, post_name, post_parent, post_author, post_status, post_modified_gmt, post_date, post_date_gmt
|
||||
FROM (
|
||||
SELECT {$wpdb->posts}.ID
|
||||
FROM {$wpdb->posts}
|
||||
{$join_filter}
|
||||
{$where}
|
||||
{$where_filter}
|
||||
ORDER BY {$wpdb->posts}.post_modified ASC LIMIT %d OFFSET %d
|
||||
)
|
||||
o JOIN {$wpdb->posts} l ON l.ID = o.ID
|
||||
";
|
||||
|
||||
$posts = $wpdb->get_results( $wpdb->prepare( $sql, $count, $offset ) );
|
||||
|
||||
$post_ids = [];
|
||||
|
||||
foreach ( $posts as $post_index => $post ) {
|
||||
$post->post_type = $post_type;
|
||||
$sanitized_post = sanitize_post( $post, 'raw' );
|
||||
$posts[ $post_index ] = new WP_Post( $sanitized_post );
|
||||
|
||||
$post_ids[] = $sanitized_post->ID;
|
||||
}
|
||||
|
||||
update_meta_cache( 'post', $post_ids );
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an SQL where clause for a given post type.
|
||||
*
|
||||
* @param string $post_type Post type slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_sql_where_clause( $post_type ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$join = '';
|
||||
$post_statuses = array_map( 'esc_sql', WPSEO_Sitemaps::get_post_statuses( $post_type ) );
|
||||
$status_where = "{$wpdb->posts}.post_status IN ('" . implode( "','", $post_statuses ) . "')";
|
||||
|
||||
// Based on WP_Query->get_posts(). R.
|
||||
if ( $post_type === 'attachment' ) {
|
||||
$join = " LEFT JOIN {$wpdb->posts} AS p2 ON ({$wpdb->posts}.post_parent = p2.ID) ";
|
||||
$parent_statuses = array_diff( $post_statuses, [ 'inherit' ] );
|
||||
$status_where = "p2.post_status IN ('" . implode( "','", $parent_statuses ) . "') AND p2.post_password = ''";
|
||||
}
|
||||
|
||||
$where_clause = "
|
||||
{$join}
|
||||
WHERE {$status_where}
|
||||
AND {$wpdb->posts}.post_type = %s
|
||||
AND {$wpdb->posts}.post_password = ''
|
||||
AND {$wpdb->posts}.post_date != '0000-00-00 00:00:00'
|
||||
";
|
||||
|
||||
return $wpdb->prepare( $where_clause, $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce array of URL parts for given post object.
|
||||
*
|
||||
* @param object $post Post object to get URL parts for.
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function get_url( $post ) {
|
||||
|
||||
$url = [];
|
||||
|
||||
/**
|
||||
* Filter the URL Yoast SEO uses in the XML sitemap.
|
||||
*
|
||||
* Note that only absolute local URLs are allowed as the check after this removes external URLs.
|
||||
*
|
||||
* @param string $url URL to use in the XML sitemap
|
||||
* @param object $post Post object for the URL.
|
||||
*/
|
||||
$url['loc'] = apply_filters( 'wpseo_xml_sitemap_post_url', get_permalink( $post ), $post );
|
||||
$link_type = YoastSEO()->helpers->url->get_link_type(
|
||||
wp_parse_url( $url['loc'] ),
|
||||
$this->get_parsed_home_url()
|
||||
);
|
||||
|
||||
/*
|
||||
* Do not include external URLs.
|
||||
*
|
||||
* {@link https://wordpress.org/plugins/page-links-to/} can rewrite permalinks to external URLs.
|
||||
*/
|
||||
if ( $link_type === SEO_Links::TYPE_EXTERNAL ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$modified = max( $post->post_modified_gmt, $post->post_date_gmt );
|
||||
|
||||
if ( $modified !== '0000-00-00 00:00:00' ) {
|
||||
$url['mod'] = $modified;
|
||||
}
|
||||
|
||||
$url['chf'] = 'daily'; // Deprecated, kept for backwards data compat. R.
|
||||
|
||||
$canonical = WPSEO_Meta::get_value( 'canonical', $post->ID );
|
||||
|
||||
if ( $canonical !== '' && $canonical !== $url['loc'] ) {
|
||||
/*
|
||||
* Let's assume that if a canonical is set for this page and it's different from
|
||||
* the URL of this post, 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;
|
||||
}
|
||||
unset( $canonical );
|
||||
|
||||
$url['pri'] = 1; // Deprecated, kept for backwards data compat. R.
|
||||
|
||||
if ( $this->include_images ) {
|
||||
$url['images'] = $this->get_image_parser()->get_images( $post );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap Cache Data object, manages sitemap data stored in cache.
|
||||
*/
|
||||
class WPSEO_Sitemap_Cache_Data implements Serializable, WPSEO_Sitemap_Cache_Data_Interface {
|
||||
|
||||
/**
|
||||
* Sitemap XML data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $sitemap = '';
|
||||
|
||||
/**
|
||||
* Status of the sitemap, usable or not.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $status = self::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Set the sitemap XML data
|
||||
*
|
||||
* @param string $sitemap XML Content of the sitemap.
|
||||
*/
|
||||
public function set_sitemap( $sitemap ) {
|
||||
|
||||
if ( ! is_string( $sitemap ) ) {
|
||||
$sitemap = '';
|
||||
}
|
||||
|
||||
$this->sitemap = $sitemap;
|
||||
|
||||
/*
|
||||
* Empty sitemap is not usable.
|
||||
*/
|
||||
if ( ! empty( $sitemap ) ) {
|
||||
$this->set_status( self::OK );
|
||||
}
|
||||
else {
|
||||
$this->set_status( self::ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status of the sitemap, is it usable.
|
||||
*
|
||||
* @param bool|string $usable Is the sitemap usable or not.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_status( $usable ) {
|
||||
|
||||
if ( $usable === self::OK ) {
|
||||
$this->status = self::OK;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $usable === self::ERROR ) {
|
||||
$this->status = self::ERROR;
|
||||
$this->sitemap = '';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->status = self::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the sitemap usable.
|
||||
*
|
||||
* @return bool True if usable, False if bad or unknown.
|
||||
*/
|
||||
public function is_usable() {
|
||||
|
||||
return $this->status === self::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the XML content of the sitemap.
|
||||
*
|
||||
* @return string The content of the sitemap.
|
||||
*/
|
||||
public function get_sitemap() {
|
||||
|
||||
return $this->sitemap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of the sitemap.
|
||||
*
|
||||
* @return string Status of the sitemap, 'ok'/'error'/'unknown'.
|
||||
*/
|
||||
public function get_status() {
|
||||
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* String representation of object.
|
||||
*
|
||||
* {@internal This magic method is only "magic" as of PHP 7.4 in which the magic method was introduced.}
|
||||
*
|
||||
* @link https://www.php.net/language.oop5.magic#object.serialize
|
||||
* @link https://wiki.php.net/rfc/custom_object_serialization
|
||||
*
|
||||
* @since 17.8.0
|
||||
*
|
||||
* @return array The data to be serialized.
|
||||
*/
|
||||
public function __serialize() { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__serializeFound
|
||||
|
||||
$data = [
|
||||
'status' => $this->status,
|
||||
'xml' => $this->sitemap,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the object.
|
||||
*
|
||||
* {@internal This magic method is only "magic" as of PHP 7.4 in which the magic method was introduced.}
|
||||
*
|
||||
* @link https://www.php.net/language.oop5.magic#object.serialize
|
||||
* @link https://wiki.php.net/rfc/custom_object_serialization
|
||||
*
|
||||
* @since 17.8.0
|
||||
*
|
||||
* @param array $data The unserialized data to use to (re)construct the object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __unserialize( $data ) { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound
|
||||
|
||||
$this->set_sitemap( $data['xml'] );
|
||||
$this->set_status( $data['status'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* String representation of object.
|
||||
*
|
||||
* {@internal The magic methods take precedence over the Serializable interface.
|
||||
* This means that in practice, this method will now only be called on PHP < 7.4.
|
||||
* For PHP 7.4 and higher, the magic methods will be used instead.}
|
||||
*
|
||||
* {@internal The Serializable interface is being phased out, in favour of the magic methods.
|
||||
* This method should be deprecated and removed and the class should no longer
|
||||
* implement the `Serializable` interface.
|
||||
* This change, however, can't be made until the minimum PHP version goes up to PHP 7.4 or higher.}
|
||||
*
|
||||
* @link http://php.net/manual/en/serializable.serialize.php
|
||||
* @link https://wiki.php.net/rfc/phase_out_serializable
|
||||
*
|
||||
* @since 5.1.0
|
||||
*
|
||||
* @return string The string representation of the object or null in C-format.
|
||||
*/
|
||||
public function serialize() {
|
||||
|
||||
return serialize( $this->__serialize() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the object.
|
||||
*
|
||||
* {@internal The magic methods take precedence over the Serializable interface.
|
||||
* This means that in practice, this method will now only be called on PHP < 7.4.
|
||||
* For PHP 7.4 and higher, the magic methods will be used instead.}
|
||||
*
|
||||
* {@internal The Serializable interface is being phased out, in favour of the magic methods.
|
||||
* This method should be deprecated and removed and the class should no longer
|
||||
* implement the `Serializable` interface.
|
||||
* This change, however, can't be made until the minimum PHP version goes up to PHP 7.4 or higher.}
|
||||
*
|
||||
* @link http://php.net/manual/en/serializable.unserialize.php
|
||||
* @link https://wiki.php.net/rfc/phase_out_serializable
|
||||
*
|
||||
* @since 5.1.0
|
||||
*
|
||||
* @param string $data The string representation of the object in C or O-format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unserialize( $data ) {
|
||||
|
||||
$data = unserialize( $data );
|
||||
$this->__unserialize( $data );
|
||||
}
|
||||
}
|
@ -0,0 +1,495 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parses images from the given post.
|
||||
*/
|
||||
class WPSEO_Sitemap_Image_Parser {
|
||||
|
||||
/**
|
||||
* Holds the home_url() value to speed up loops.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $home_url = '';
|
||||
|
||||
/**
|
||||
* Holds site URL hostname.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $host = '';
|
||||
|
||||
/**
|
||||
* Holds site URL protocol.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $scheme = 'http';
|
||||
|
||||
/**
|
||||
* Cached set of attachments for multiple posts.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attachments = [];
|
||||
|
||||
/**
|
||||
* Holds blog charset value for use in DOM parsing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Set up URL properties for reuse.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->home_url = home_url();
|
||||
$parsed_home = wp_parse_url( $this->home_url );
|
||||
|
||||
if ( ! empty( $parsed_home['host'] ) ) {
|
||||
$this->host = str_replace( 'www.', '', $parsed_home['host'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $parsed_home['scheme'] ) ) {
|
||||
$this->scheme = $parsed_home['scheme'];
|
||||
}
|
||||
|
||||
$this->charset = esc_attr( get_bloginfo( 'charset' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get set of image data sets for the given post.
|
||||
*
|
||||
* @param object $post Post object to get images for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_images( $post ) {
|
||||
|
||||
$images = [];
|
||||
|
||||
if ( ! is_object( $post ) ) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $post->ID );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
|
||||
$src = $this->get_absolute_url( $this->image_url( $thumbnail_id ) );
|
||||
$images[] = $this->get_image_item( $post, $src );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_sitemap_content_before_parse_html_images' - Filters the post content
|
||||
* before it is parsed for images.
|
||||
*
|
||||
* @param string $content The raw/unprocessed post content.
|
||||
*/
|
||||
$content = apply_filters( 'wpseo_sitemap_content_before_parse_html_images', $post->post_content );
|
||||
|
||||
$unfiltered_images = $this->parse_html_images( $content );
|
||||
|
||||
foreach ( $unfiltered_images as $image ) {
|
||||
$images[] = $this->get_image_item( $post, $image['src'] );
|
||||
}
|
||||
|
||||
foreach ( $this->parse_galleries( $content, $post->ID ) as $attachment ) {
|
||||
$src = $this->get_absolute_url( $this->image_url( $attachment->ID ) );
|
||||
$images[] = $this->get_image_item( $post, $src );
|
||||
}
|
||||
|
||||
if ( $post->post_type === 'attachment' && wp_attachment_is_image( $post ) ) {
|
||||
$src = $this->get_absolute_url( $this->image_url( $post->ID ) );
|
||||
$images[] = $this->get_image_item( $post, $src );
|
||||
}
|
||||
|
||||
foreach ( $images as $key => $image ) {
|
||||
|
||||
if ( empty( $image['src'] ) ) {
|
||||
unset( $images[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter images to be included for the post in XML sitemap.
|
||||
*
|
||||
* @param array $images Array of image items.
|
||||
* @param int $post_id ID of the post.
|
||||
*/
|
||||
$images = apply_filters( 'wpseo_sitemap_urlimages', $images, $post->ID );
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the images in the term description.
|
||||
*
|
||||
* @param object $term Term to get images from description for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_term_images( $term ) {
|
||||
|
||||
$images = $this->parse_html_images( $term->description );
|
||||
|
||||
foreach ( $this->parse_galleries( $term->description ) as $attachment ) {
|
||||
|
||||
$images[] = [
|
||||
'src' => $this->get_absolute_url( $this->image_url( $attachment->ID ) ),
|
||||
];
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse `<img />` tags in content.
|
||||
*
|
||||
* @param string $content Content string to parse.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parse_html_images( $content ) {
|
||||
|
||||
$images = [];
|
||||
|
||||
if ( ! class_exists( 'DOMDocument' ) ) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
// Prevent DOMDocument from bubbling warnings about invalid HTML.
|
||||
libxml_use_internal_errors( true );
|
||||
|
||||
$post_dom = new DOMDocument();
|
||||
$post_dom->loadHTML( '<?xml encoding="' . $this->charset . '">' . $content );
|
||||
|
||||
// Clear the errors, so they don't get kept in memory.
|
||||
libxml_clear_errors();
|
||||
|
||||
/**
|
||||
* Image attribute.
|
||||
*
|
||||
* @var DOMElement $img
|
||||
*/
|
||||
foreach ( $post_dom->getElementsByTagName( 'img' ) as $img ) {
|
||||
|
||||
$src = $img->getAttribute( 'src' );
|
||||
|
||||
if ( empty( $src ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $img->getAttribute( 'class' );
|
||||
|
||||
if ( // This detects WP-inserted images, which we need to upsize. R.
|
||||
! empty( $class )
|
||||
&& ( strpos( $class, 'size-full' ) === false )
|
||||
&& preg_match( '|wp-image-(?P<id>\d+)|', $class, $matches )
|
||||
&& get_post_status( $matches['id'] )
|
||||
) {
|
||||
$query_params = wp_parse_url( $src, PHP_URL_QUERY );
|
||||
$src = $this->image_url( $matches['id'] );
|
||||
|
||||
if ( $query_params ) {
|
||||
$src .= '?' . $query_params;
|
||||
}
|
||||
}
|
||||
|
||||
$src = $this->get_absolute_url( $src );
|
||||
|
||||
if ( strpos( $src, $this->host ) === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $src !== esc_url( $src, null, 'attribute' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$images[] = [
|
||||
'src' => $src,
|
||||
];
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse gallery shortcodes in a given content.
|
||||
*
|
||||
* @param string $content Content string.
|
||||
* @param int $post_id Optional. ID of post being parsed.
|
||||
*
|
||||
* @return array Set of attachment objects.
|
||||
*/
|
||||
protected function parse_galleries( $content, $post_id = 0 ) {
|
||||
|
||||
$attachments = [];
|
||||
$galleries = $this->get_content_galleries( $content );
|
||||
|
||||
foreach ( $galleries as $gallery ) {
|
||||
|
||||
$id = $post_id;
|
||||
|
||||
if ( ! empty( $gallery['id'] ) ) {
|
||||
$id = intval( $gallery['id'] );
|
||||
}
|
||||
|
||||
// Forked from core gallery_shortcode() to have exact same logic. R.
|
||||
if ( ! empty( $gallery['ids'] ) ) {
|
||||
$gallery['include'] = $gallery['ids'];
|
||||
}
|
||||
|
||||
$gallery_attachments = $this->get_gallery_attachments( $id, $gallery );
|
||||
|
||||
$attachments = array_merge( $attachments, $gallery_attachments );
|
||||
}
|
||||
|
||||
return array_unique( $attachments, SORT_REGULAR );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves galleries from the passed content.
|
||||
*
|
||||
* Forked from core to skip executing shortcodes for performance.
|
||||
*
|
||||
* @param string $content Content to parse for shortcodes.
|
||||
*
|
||||
* @return array A list of arrays, each containing gallery data.
|
||||
*/
|
||||
protected function get_content_galleries( $content ) {
|
||||
|
||||
$galleries = [];
|
||||
|
||||
if ( ! preg_match_all( '/' . get_shortcode_regex( [ 'gallery' ] ) . '/s', $content, $matches, PREG_SET_ORDER ) ) {
|
||||
return $galleries;
|
||||
}
|
||||
|
||||
foreach ( $matches as $shortcode ) {
|
||||
|
||||
$attributes = shortcode_parse_atts( $shortcode[3] );
|
||||
|
||||
if ( $attributes === '' ) { // Valid shortcode without any attributes. R.
|
||||
$attributes = [];
|
||||
}
|
||||
|
||||
$galleries[] = $attributes;
|
||||
}
|
||||
|
||||
return $galleries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image item array with filters applied.
|
||||
*
|
||||
* @param WP_Post $post Post object for the context.
|
||||
* @param string $src Image URL.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_image_item( $post, $src ) {
|
||||
|
||||
$image = [];
|
||||
|
||||
/**
|
||||
* Filter image URL to be included in XML sitemap for the post.
|
||||
*
|
||||
* @param string $src Image URL.
|
||||
* @param object $post Post object.
|
||||
*/
|
||||
$image['src'] = apply_filters( 'wpseo_xml_sitemap_img_src', $src, $post );
|
||||
|
||||
/**
|
||||
* Filter image data to be included in XML sitemap for the post.
|
||||
*
|
||||
* @param array $image {
|
||||
* Array of image data.
|
||||
*
|
||||
* @type string $src Image URL.
|
||||
* }
|
||||
*
|
||||
* @param object $post Post object.
|
||||
*/
|
||||
return apply_filters( 'wpseo_xml_sitemap_img', $image, $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attached image URL with filters applied. Adapted from core for speed.
|
||||
*
|
||||
* @param int $post_id ID of the post.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function image_url( $post_id ) {
|
||||
|
||||
static $uploads;
|
||||
|
||||
if ( empty( $uploads ) ) {
|
||||
$uploads = wp_upload_dir();
|
||||
}
|
||||
|
||||
if ( $uploads['error'] !== false ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$file = get_post_meta( $post_id, '_wp_attached_file', true );
|
||||
|
||||
if ( empty( $file ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check that the upload base exists in the file location.
|
||||
if ( strpos( $file, $uploads['basedir'] ) === 0 ) {
|
||||
$src = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
|
||||
}
|
||||
elseif ( strpos( $file, 'wp-content/uploads' ) !== false ) {
|
||||
$src = $uploads['baseurl'] . substr( $file, ( strpos( $file, 'wp-content/uploads' ) + 18 ) );
|
||||
}
|
||||
else {
|
||||
// It's a newly uploaded file, therefore $file is relative to the baseurl.
|
||||
$src = $uploads['baseurl'] . '/' . $file;
|
||||
}
|
||||
|
||||
return apply_filters( 'wp_get_attachment_url', $src, $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make absolute URL for domain or protocol-relative one.
|
||||
*
|
||||
* @param string $src URL to process.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_absolute_url( $src ) {
|
||||
|
||||
if ( empty( $src ) || ! is_string( $src ) ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
if ( YoastSEO()->helpers->url->is_relative( $src ) === true ) {
|
||||
|
||||
if ( $src[0] !== '/' ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
// The URL is relative, we'll have to make it absolute.
|
||||
return $this->home_url . $src;
|
||||
}
|
||||
|
||||
if ( strpos( $src, 'http' ) !== 0 ) {
|
||||
// Protocol relative URL, we add the scheme as the standard requires a protocol.
|
||||
return $this->scheme . ':' . $src;
|
||||
}
|
||||
|
||||
return $src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachments for a gallery.
|
||||
*
|
||||
* @param int $id The post ID.
|
||||
* @param array $gallery The gallery config.
|
||||
*
|
||||
* @return array The selected attachments.
|
||||
*/
|
||||
protected function get_gallery_attachments( $id, $gallery ) {
|
||||
|
||||
// When there are attachments to include.
|
||||
if ( ! empty( $gallery['include'] ) ) {
|
||||
return $this->get_gallery_attachments_for_included( $gallery['include'] );
|
||||
}
|
||||
|
||||
// When $id is empty, just return empty array.
|
||||
if ( empty( $id ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->get_gallery_attachments_for_parent( $id, $gallery );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachments for the given ID.
|
||||
*
|
||||
* @param int $id The post ID.
|
||||
* @param array $gallery The gallery config.
|
||||
*
|
||||
* @return array The selected attachments.
|
||||
*/
|
||||
protected function get_gallery_attachments_for_parent( $id, $gallery ) {
|
||||
$query = [
|
||||
'posts_per_page' => -1,
|
||||
'post_parent' => $id,
|
||||
];
|
||||
|
||||
// When there are posts that should be excluded from result set.
|
||||
if ( ! empty( $gallery['exclude'] ) ) {
|
||||
$query['post__not_in'] = wp_parse_id_list( $gallery['exclude'] );
|
||||
}
|
||||
|
||||
return $this->get_attachments( $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with attachments for the post IDs that will be included.
|
||||
*
|
||||
* @param array $included_ids Array with IDs to include.
|
||||
*
|
||||
* @return array The found attachments.
|
||||
*/
|
||||
protected function get_gallery_attachments_for_included( $included_ids ) {
|
||||
$ids_to_include = wp_parse_id_list( $included_ids );
|
||||
$attachments = $this->get_attachments(
|
||||
[
|
||||
'posts_per_page' => count( $ids_to_include ),
|
||||
'post__in' => $ids_to_include,
|
||||
]
|
||||
);
|
||||
|
||||
$gallery_attachments = [];
|
||||
foreach ( $attachments as $val ) {
|
||||
$gallery_attachments[ $val->ID ] = $val;
|
||||
}
|
||||
|
||||
return $gallery_attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attachments.
|
||||
*
|
||||
* @param array $args Array with query args.
|
||||
*
|
||||
* @return array The found attachments.
|
||||
*/
|
||||
protected function get_attachments( $args ) {
|
||||
$default_args = [
|
||||
'post_status' => 'inherit',
|
||||
'post_type' => 'attachment',
|
||||
'post_mime_type' => 'image',
|
||||
|
||||
// Defaults taken from function get_posts.
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'meta_key' => '',
|
||||
'meta_value' => '',
|
||||
'suppress_filters' => true,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
|
||||
$args = wp_parse_args( $args, $default_args );
|
||||
|
||||
$get_attachments = new WP_Query();
|
||||
return $get_attachments->query( $args );
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\XML Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class that handles the Admin side of XML sitemaps.
|
||||
*/
|
||||
class WPSEO_Sitemaps_Admin {
|
||||
|
||||
/**
|
||||
* Post_types that are being imported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $importing_post_types = [];
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'transition_post_status', [ $this, 'status_transition' ], 10, 3 );
|
||||
add_action( 'admin_footer', [ $this, 'status_transition_bulk_finished' ] );
|
||||
|
||||
WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'wpseo_titles', '' );
|
||||
WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'wpseo', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooked into transition_post_status. Will initiate search engine pings
|
||||
* if the post is being published, is a post type that a sitemap is built for
|
||||
* and is a post that is included in sitemaps.
|
||||
*
|
||||
* @param string $new_status New post status.
|
||||
* @param string $old_status Old post status.
|
||||
* @param \WP_Post $post Post object.
|
||||
*/
|
||||
public function status_transition( $new_status, $old_status, $post ) {
|
||||
if ( $new_status !== 'publish' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( defined( 'WP_IMPORTING' ) ) {
|
||||
$this->status_transition_bulk( $new_status, $old_status, $post );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$post_type = get_post_type( $post );
|
||||
|
||||
wp_cache_delete( 'lastpostmodified:gmt:' . $post_type, 'timeinfo' ); // #17455.
|
||||
|
||||
// Not something we're interested in.
|
||||
if ( $post_type === 'nav_menu_item' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the post type is excluded in options, we can stop.
|
||||
if ( WPSEO_Options::get( 'noindex-' . $post_type, false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! YoastSEO()->helpers->environment->is_production_mode() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ping_search_engines();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify Google of the updated sitemap.
|
||||
*/
|
||||
public function ping_search_engines() {
|
||||
|
||||
if ( get_option( 'blog_public' ) === '0' ) { // Don't ping if blog is not public.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_allow_xml_sitemap_ping' - Check if pinging is not allowed (allowed by default).
|
||||
*
|
||||
* @api boolean $allow_ping The boolean that is set to true by default.
|
||||
*/
|
||||
if ( apply_filters( 'wpseo_allow_xml_sitemap_ping', true ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = rawurlencode( WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ) );
|
||||
|
||||
// Ping Google about our sitemap change.
|
||||
wp_remote_get( 'https://www.google.com/ping?sitemap=' . $url, [ 'blocking' => false ] );
|
||||
|
||||
if ( ! defined( 'WPSEO_PREMIUM_FILE' ) || WPSEO_Options::get( 'enable_index_now' ) === false ) {
|
||||
wp_remote_get( 'https://www.bing.com/ping?sitemap=' . $url, [ 'blocking' => false ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* While bulk importing, just save unique post_types.
|
||||
*
|
||||
* When importing is done, if we have a post_type that is saved in the sitemap
|
||||
* try to ping the search engines.
|
||||
*
|
||||
* @param string $new_status New post status.
|
||||
* @param string $old_status Old post status.
|
||||
* @param \WP_Post $post Post object.
|
||||
*/
|
||||
private function status_transition_bulk( $new_status, $old_status, $post ) {
|
||||
$this->importing_post_types[] = get_post_type( $post );
|
||||
$this->importing_post_types = array_unique( $this->importing_post_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* After import finished, walk through imported post_types and update info.
|
||||
*/
|
||||
public function status_transition_bulk_finished() {
|
||||
if ( ! defined( 'WP_IMPORTING' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $this->importing_post_types ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ping_search_engines = false;
|
||||
|
||||
foreach ( $this->importing_post_types as $post_type ) {
|
||||
wp_cache_delete( 'lastpostmodified:gmt:' . $post_type, 'timeinfo' ); // #17455.
|
||||
|
||||
// Just have the cache deleted for nav_menu_item.
|
||||
if ( $post_type === 'nav_menu_item' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( WPSEO_Options::get( 'noindex-' . $post_type, false ) === false ) {
|
||||
$ping_search_engines = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing to do.
|
||||
if ( $ping_search_engines === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WP_CACHE ) {
|
||||
do_action( 'wpseo_hit_sitemap_index' );
|
||||
}
|
||||
|
||||
$this->ping_search_engines();
|
||||
}
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handles storage keys for sitemaps caching and invalidation.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
class WPSEO_Sitemaps_Cache_Validator {
|
||||
|
||||
/**
|
||||
* Prefix of the transient key for sitemap caches.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const STORAGE_KEY_PREFIX = 'yst_sm_';
|
||||
|
||||
/**
|
||||
* Name of the option that holds the global validation value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VALIDATION_GLOBAL_KEY = 'wpseo_sitemap_cache_validator_global';
|
||||
|
||||
/**
|
||||
* The format which creates the key of the option that holds the type validation value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VALIDATION_TYPE_KEY_FORMAT = 'wpseo_sitemap_%s_cache_validator';
|
||||
|
||||
/**
|
||||
* Get the cache key for a certain type and page.
|
||||
*
|
||||
* A type of cache would be something like 'page', 'post' or 'video'.
|
||||
*
|
||||
* Example key format for sitemap type "post", page 1: wpseo_sitemap_post_1:akfw3e_23azBa .
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string|null $type The type to get the key for. Null or self::SITEMAP_INDEX_TYPE for index cache.
|
||||
* @param int $page The page of cache to get the key for.
|
||||
*
|
||||
* @return bool|string The key where the cache is stored on. False if the key could not be generated.
|
||||
*/
|
||||
public static function get_storage_key( $type = null, $page = 1 ) {
|
||||
|
||||
// Using SITEMAP_INDEX_TYPE for sitemap index cache.
|
||||
$type = is_null( $type ) ? WPSEO_Sitemaps::SITEMAP_INDEX_TYPE : $type;
|
||||
|
||||
$global_cache_validator = self::get_validator();
|
||||
$type_cache_validator = self::get_validator( $type );
|
||||
|
||||
$prefix = self::STORAGE_KEY_PREFIX;
|
||||
$postfix = sprintf( '_%d:%s_%s', $page, $global_cache_validator, $type_cache_validator );
|
||||
|
||||
try {
|
||||
$type = self::truncate_type( $type, $prefix, $postfix );
|
||||
} catch ( OutOfBoundsException $exception ) {
|
||||
// Maybe do something with the exception, for now just mark as invalid.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build key.
|
||||
$full_key = $prefix . $type . $postfix;
|
||||
|
||||
return $full_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the type is over length make sure we compact it so we don't have any database problems.
|
||||
*
|
||||
* When there are more 'extremely long' post types, changes are they have variations in either the start or ending.
|
||||
* Because of this, we cut out the excess in the middle which should result in less chance of collision.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type The type of sitemap to be used.
|
||||
* @param string $prefix The part before the type in the cache key. Only the length is used.
|
||||
* @param string $postfix The part after the type in the cache key. Only the length is used.
|
||||
*
|
||||
* @return string The type with a safe length to use
|
||||
*
|
||||
* @throws OutOfRangeException When there is less than 15 characters of space for a key that is originally longer.
|
||||
*/
|
||||
public static function truncate_type( $type, $prefix = '', $postfix = '' ) {
|
||||
/*
|
||||
* This length has been restricted by the database column length of 64 in the past.
|
||||
* The prefix added by WordPress is '_transient_' because we are saving to a transient.
|
||||
* We need to use a timeout on the transient, otherwise the values get autoloaded, this adds
|
||||
* another restriction to the length.
|
||||
*/
|
||||
$max_length = 45; // 64 - 19 ('_transient_timeout_')
|
||||
$max_length -= strlen( $prefix );
|
||||
$max_length -= strlen( $postfix );
|
||||
|
||||
if ( strlen( $type ) > $max_length ) {
|
||||
|
||||
if ( $max_length < 15 ) {
|
||||
/*
|
||||
* If this happens the most likely cause is a page number that is too high.
|
||||
*
|
||||
* So this would not happen unintentionally.
|
||||
* Either by trying to cause a high server load, finding backdoors or misconfiguration.
|
||||
*/
|
||||
throw new OutOfRangeException(
|
||||
__(
|
||||
'Trying to build the sitemap cache key, but the postfix and prefix combination leaves too little room to do this. You are probably requesting a page that is way out of the expected range.',
|
||||
'wordpress-seo'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$half = ( $max_length / 2 );
|
||||
|
||||
$first_part = substr( $type, 0, ( ceil( $half ) - 1 ) );
|
||||
$last_part = substr( $type, ( 1 - floor( $half ) ) );
|
||||
|
||||
$type = $first_part . '..' . $last_part;
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate sitemap cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string|null $type The type to get the key for. Null for all caches.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidate_storage( $type = null ) {
|
||||
|
||||
// Global validator gets cleared when no type is provided.
|
||||
$old_validator = null;
|
||||
|
||||
// Get the current type validator.
|
||||
if ( ! is_null( $type ) ) {
|
||||
$old_validator = self::get_validator( $type );
|
||||
}
|
||||
|
||||
// Refresh validator.
|
||||
self::create_validator( $type );
|
||||
|
||||
if ( ! wp_using_ext_object_cache() ) {
|
||||
// Clean up current cache from the database.
|
||||
self::cleanup_database( $type, $old_validator );
|
||||
}
|
||||
|
||||
// External object cache pushes old and unretrieved items out by itself so we don't have to do anything for that.
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup invalidated database cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string|null $type The type of sitemap to clear cache for.
|
||||
* @param string|null $validator The validator to clear cache of.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function cleanup_database( $type = null, $validator = null ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if ( is_null( $type ) ) {
|
||||
// Clear all cache if no type is provided.
|
||||
$like = sprintf( '%s%%', self::STORAGE_KEY_PREFIX );
|
||||
}
|
||||
else {
|
||||
// Clear type cache for all type keys.
|
||||
$like = sprintf( '%1$s%2$s_%%', self::STORAGE_KEY_PREFIX, $type );
|
||||
}
|
||||
|
||||
/*
|
||||
* Add slashes to the LIKE "_" single character wildcard.
|
||||
*
|
||||
* We can't use `esc_like` here because we need the % in the query.
|
||||
*/
|
||||
$where = [];
|
||||
$where[] = sprintf( "option_name LIKE '%s'", addcslashes( '_transient_' . $like, '_' ) );
|
||||
$where[] = sprintf( "option_name LIKE '%s'", addcslashes( '_transient_timeout_' . $like, '_' ) );
|
||||
|
||||
// Delete transients.
|
||||
$query = sprintf( 'DELETE FROM %1$s WHERE %2$s', $wpdb->options, implode( ' OR ', $where ) );
|
||||
$wpdb->query( $query );
|
||||
|
||||
wp_cache_delete( 'alloptions', 'options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current cache validator.
|
||||
*
|
||||
* Without the type the global validator is returned.
|
||||
* This can invalidate -all- keys in cache at once.
|
||||
*
|
||||
* With the type parameter the validator for that specific type can be invalidated.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Provide a type for a specific type validator, empty for global validator.
|
||||
*
|
||||
* @return string|null The validator for the supplied type.
|
||||
*/
|
||||
public static function get_validator( $type = '' ) {
|
||||
|
||||
$key = self::get_validator_key( $type );
|
||||
|
||||
$current = get_option( $key, null );
|
||||
if ( ! is_null( $current ) ) {
|
||||
return $current;
|
||||
}
|
||||
|
||||
if ( self::create_validator( $type ) ) {
|
||||
return self::get_validator( $type );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache validator option key for the specified type.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Provide a type for a specific type validator, empty for global validator.
|
||||
*
|
||||
* @return string Validator to be used to generate the cache key.
|
||||
*/
|
||||
public static function get_validator_key( $type = '' ) {
|
||||
|
||||
if ( empty( $type ) ) {
|
||||
return self::VALIDATION_GLOBAL_KEY;
|
||||
}
|
||||
|
||||
return sprintf( self::VALIDATION_TYPE_KEY_FORMAT, $type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the cache validator value.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Provide a type for a specific type validator, empty for global validator.
|
||||
*
|
||||
* @return bool True if validator key has been saved as option.
|
||||
*/
|
||||
public static function create_validator( $type = '' ) {
|
||||
|
||||
$key = self::get_validator_key( $type );
|
||||
|
||||
// Generate new validator.
|
||||
$microtime = microtime();
|
||||
|
||||
// Remove space.
|
||||
list( $milliseconds, $seconds ) = explode( ' ', $microtime );
|
||||
|
||||
// Transients are purged every 24h.
|
||||
$seconds = ( $seconds % DAY_IN_SECONDS );
|
||||
$milliseconds = intval( substr( $milliseconds, 2, 3 ), 10 );
|
||||
|
||||
// Combine seconds and milliseconds and convert to integer.
|
||||
$validator = intval( $seconds . '' . $milliseconds, 10 );
|
||||
|
||||
// Apply base 61 encoding.
|
||||
$compressed = self::convert_base10_to_base61( $validator );
|
||||
|
||||
return update_option( $key, $compressed, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode to base61 format.
|
||||
*
|
||||
* This is base64 (numeric + alpha + alpha upper case) without the 0.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param int $base10 The number that has to be converted to base 61.
|
||||
*
|
||||
* @return string Base 61 converted string.
|
||||
*
|
||||
* @throws InvalidArgumentException When the input is not an integer.
|
||||
*/
|
||||
public static function convert_base10_to_base61( $base10 ) {
|
||||
|
||||
if ( ! is_int( $base10 ) ) {
|
||||
throw new InvalidArgumentException( __( 'Expected an integer as input.', 'wordpress-seo' ) );
|
||||
}
|
||||
|
||||
// Characters that will be used in the conversion.
|
||||
$characters = '123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$length = strlen( $characters );
|
||||
|
||||
$remainder = $base10;
|
||||
$output = '';
|
||||
|
||||
do {
|
||||
// Building from right to left in the result.
|
||||
$index = ( $remainder % $length );
|
||||
|
||||
// Prepend the character to the output.
|
||||
$output = $characters[ $index ] . $output;
|
||||
|
||||
// Determine the remainder after removing the applied number.
|
||||
$remainder = floor( $remainder / $length );
|
||||
|
||||
// Keep doing it until we have no remainder left.
|
||||
} while ( $remainder );
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
@ -0,0 +1,353 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handles sitemaps caching and invalidation.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
class WPSEO_Sitemaps_Cache {
|
||||
|
||||
/**
|
||||
* Holds the options that, when updated, should cause the cache to clear.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $cache_clear = [];
|
||||
|
||||
/**
|
||||
* Mirror of enabled status for static calls.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $is_enabled = false;
|
||||
|
||||
/**
|
||||
* Holds the flag to clear all cache.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $clear_all = false;
|
||||
|
||||
/**
|
||||
* Holds the array of types to clear.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $clear_types = [];
|
||||
|
||||
/**
|
||||
* Hook methods for invalidation on necessary events.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'init', [ $this, 'init' ] );
|
||||
|
||||
add_action( 'deleted_term_relationships', [ __CLASS__, 'invalidate' ] );
|
||||
|
||||
add_action( 'update_option', [ __CLASS__, 'clear_on_option_update' ] );
|
||||
|
||||
add_action( 'edited_terms', [ __CLASS__, 'invalidate_helper' ], 10, 2 );
|
||||
add_action( 'clean_term_cache', [ __CLASS__, 'invalidate_helper' ], 10, 2 );
|
||||
add_action( 'clean_object_term_cache', [ __CLASS__, 'invalidate_helper' ], 10, 2 );
|
||||
|
||||
add_action( 'user_register', [ __CLASS__, 'invalidate_author' ] );
|
||||
add_action( 'delete_user', [ __CLASS__, 'invalidate_author' ] );
|
||||
|
||||
add_action( 'shutdown', [ __CLASS__, 'clear_queued' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup context for static calls.
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
self::$is_enabled = $this->is_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* If cache is enabled.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled() {
|
||||
|
||||
/**
|
||||
* Filter if XML sitemap transient cache is enabled.
|
||||
*
|
||||
* @param bool $unsigned Enable cache or not, defaults to true.
|
||||
*/
|
||||
return apply_filters( 'wpseo_enable_xml_sitemap_transient_caching', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sitemap page from cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page Page number to retrieve.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function get_sitemap( $type, $page ) {
|
||||
|
||||
$transient_key = WPSEO_Sitemaps_Cache_Validator::get_storage_key( $type, $page );
|
||||
if ( $transient_key === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_transient( $transient_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sitemap that is cached.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page Page number to retrieve.
|
||||
*
|
||||
* @return WPSEO_Sitemap_Cache_Data|null Null on no cache found otherwise object containing sitemap and meta data.
|
||||
*/
|
||||
public function get_sitemap_data( $type, $page ) {
|
||||
|
||||
$sitemap = $this->get_sitemap( $type, $page );
|
||||
|
||||
if ( empty( $sitemap ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unserialize Cache Data object as is_serialized() doesn't recognize classes in C format.
|
||||
* This work-around should no longer be needed once the minimum PHP version has gone up to PHP 7.4,
|
||||
* as the `WPSEO_Sitemap_Cache_Data` class uses O format serialization in PHP 7.4 and higher.
|
||||
*
|
||||
* @link https://wiki.php.net/rfc/custom_object_serialization
|
||||
*/
|
||||
if ( is_string( $sitemap ) && strpos( $sitemap, 'C:24:"WPSEO_Sitemap_Cache_Data"' ) === 0 ) {
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- Can't be avoided due to how WP stores options.
|
||||
$sitemap = unserialize( $sitemap );
|
||||
}
|
||||
|
||||
// What we expect it to be if it is set.
|
||||
if ( $sitemap instanceof WPSEO_Sitemap_Cache_Data_Interface ) {
|
||||
return $sitemap;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the sitemap page from cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page Page number to store.
|
||||
* @param string $sitemap Sitemap body to store.
|
||||
* @param bool $usable Is this a valid sitemap or a cache of an invalid sitemap.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function store_sitemap( $type, $page, $sitemap, $usable = true ) {
|
||||
|
||||
$transient_key = WPSEO_Sitemaps_Cache_Validator::get_storage_key( $type, $page );
|
||||
|
||||
if ( $transient_key === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$status = ( $usable ) ? WPSEO_Sitemap_Cache_Data::OK : WPSEO_Sitemap_Cache_Data::ERROR;
|
||||
|
||||
$sitemap_data = new WPSEO_Sitemap_Cache_Data();
|
||||
$sitemap_data->set_sitemap( $sitemap );
|
||||
$sitemap_data->set_status( $status );
|
||||
|
||||
return set_transient( $transient_key, $sitemap_data, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache transients for index and specific type.
|
||||
*
|
||||
* Always deletes the main index sitemaps cache, as that's always invalidated by any other change.
|
||||
*
|
||||
* @since 1.5.4
|
||||
* @since 3.2 Changed from function wpseo_invalidate_sitemap_cache() to method in this class.
|
||||
*
|
||||
* @param string $type Sitemap type to invalidate.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidate( $type ) {
|
||||
|
||||
self::clear( [ $type ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to invalidate in hooks where type is passed as second argument.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param int $unused Unused term ID value.
|
||||
* @param string $type Taxonomy to invalidate.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidate_helper( $unused, $type ) {
|
||||
|
||||
if (
|
||||
WPSEO_Options::get( 'noindex-' . $type ) === false
|
||||
|| WPSEO_Options::get( 'noindex-tax-' . $type ) === false
|
||||
) {
|
||||
self::invalidate( $type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate sitemap cache for authors.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
*
|
||||
* @return bool True if the sitemap was properly invalidated. False otherwise.
|
||||
*/
|
||||
public static function invalidate_author( $user_id ) {
|
||||
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
|
||||
if ( $user === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( current_action() === 'user_register' ) {
|
||||
update_user_meta( $user_id, '_yoast_wpseo_profile_updated', time() );
|
||||
}
|
||||
|
||||
if ( empty( $user->roles ) || in_array( 'subscriber', $user->roles, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self::invalidate( 'author' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate sitemap cache for the post type of a post.
|
||||
*
|
||||
* Don't invalidate for revisions.
|
||||
*
|
||||
* @since 1.5.4
|
||||
* @since 3.2 Changed from function wpseo_invalidate_sitemap_cache_on_save_post() to method in this class.
|
||||
*
|
||||
* @param int $post_id Post ID to invalidate type for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function invalidate_post( $post_id ) {
|
||||
|
||||
if ( wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::invalidate( get_post_type( $post_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache transients for given sitemaps types or all by default.
|
||||
*
|
||||
* @since 1.8.0
|
||||
* @since 3.2 Moved from WPSEO_Utils to this class.
|
||||
*
|
||||
* @param array $types Set of sitemap types to delete cache transients for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear( $types = [] ) {
|
||||
|
||||
if ( ! self::$is_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No types provided, clear all.
|
||||
if ( empty( $types ) ) {
|
||||
self::$clear_all = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Always invalidate the index sitemap as well.
|
||||
if ( ! in_array( WPSEO_Sitemaps::SITEMAP_INDEX_TYPE, $types, true ) ) {
|
||||
array_unshift( $types, WPSEO_Sitemaps::SITEMAP_INDEX_TYPE );
|
||||
}
|
||||
|
||||
foreach ( $types as $type ) {
|
||||
if ( ! in_array( $type, self::$clear_types, true ) ) {
|
||||
self::$clear_types[] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate storage for cache types queued to clear.
|
||||
*/
|
||||
public static function clear_queued() {
|
||||
|
||||
if ( self::$clear_all ) {
|
||||
|
||||
WPSEO_Sitemaps_Cache_Validator::invalidate_storage();
|
||||
self::$clear_all = false;
|
||||
self::$clear_types = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( self::$clear_types as $type ) {
|
||||
WPSEO_Sitemaps_Cache_Validator::invalidate_storage( $type );
|
||||
}
|
||||
|
||||
self::$clear_types = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a hook that when given option is updated, the cache is cleared.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @param string $type Sitemap type.
|
||||
*/
|
||||
public static function register_clear_on_option_update( $option, $type = '' ) {
|
||||
|
||||
self::$cache_clear[ $option ] = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the transient cache when a given option is updated, if that option has been registered before.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string $option The option name that's being updated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_on_option_update( $option ) {
|
||||
|
||||
if ( array_key_exists( $option, self::$cache_clear ) ) {
|
||||
|
||||
if ( empty( self::$cache_clear[ $option ] ) ) {
|
||||
// Clear all caches.
|
||||
self::clear();
|
||||
}
|
||||
else {
|
||||
// Clear specific provided type(s).
|
||||
$types = (array) self::$cache_clear[ $option ];
|
||||
self::clear( $types );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,347 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Renders XML output for sitemaps.
|
||||
*/
|
||||
class WPSEO_Sitemaps_Renderer {
|
||||
|
||||
/**
|
||||
* XSL stylesheet for styling a sitemap for web browsers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $stylesheet = '';
|
||||
|
||||
/**
|
||||
* Holds the get_bloginfo( 'charset' ) value to reuse for performance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Holds charset of output, might be converted.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $output_charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* If data encoding needs to be converted for output.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $needs_conversion = false;
|
||||
|
||||
/**
|
||||
* Set up object properties.
|
||||
*/
|
||||
public function __construct() {
|
||||
$stylesheet_url = preg_replace( '/(^http[s]?:)/', '', $this->get_xsl_url() );
|
||||
$this->stylesheet = '<?xml-stylesheet type="text/xsl" href="' . esc_url( $stylesheet_url ) . '"?>';
|
||||
$this->charset = get_bloginfo( 'charset' );
|
||||
$this->output_charset = $this->charset;
|
||||
|
||||
if (
|
||||
$this->charset !== 'UTF-8'
|
||||
&& function_exists( 'mb_list_encodings' )
|
||||
&& in_array( $this->charset, mb_list_encodings(), true )
|
||||
) {
|
||||
$this->output_charset = 'UTF-8';
|
||||
}
|
||||
|
||||
$this->needs_conversion = $this->output_charset !== $this->charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the sitemap index.
|
||||
*
|
||||
* @param array $links Set of sitemaps index links.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_index( $links ) {
|
||||
|
||||
$xml = '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
||||
|
||||
foreach ( $links as $link ) {
|
||||
$xml .= $this->sitemap_index_url( $link );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to append sitemaps to the index.
|
||||
*
|
||||
* @param string $index String to append to sitemaps index, defaults to empty.
|
||||
*/
|
||||
$xml .= apply_filters( 'wpseo_sitemap_index', '' );
|
||||
$xml .= '</sitemapindex>';
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the sitemap.
|
||||
*
|
||||
* @param array $links Set of sitemap links.
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $current_page Current sitemap page number.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_sitemap( $links, $type, $current_page ) {
|
||||
|
||||
$urlset = '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" '
|
||||
. 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd '
|
||||
. 'http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" '
|
||||
. 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
||||
|
||||
/**
|
||||
* Filters the `urlset` for a sitemap by type.
|
||||
*
|
||||
* @api string $urlset The output for the sitemap's `urlset`.
|
||||
*/
|
||||
$xml = apply_filters( "wpseo_sitemap_{$type}_urlset", $urlset );
|
||||
|
||||
foreach ( $links as $url ) {
|
||||
$xml .= $this->sitemap_url( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to add extra URLs to the XML sitemap by type.
|
||||
*
|
||||
* Only runs for the first page, not on all.
|
||||
*
|
||||
* @param string $content String content to add, defaults to empty.
|
||||
*/
|
||||
if ( $current_page === 1 ) {
|
||||
$xml .= apply_filters( "wpseo_sitemap_{$type}_content", '' );
|
||||
}
|
||||
|
||||
$xml .= '</urlset>';
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce final XML output with debug information.
|
||||
*
|
||||
* @param string $sitemap Sitemap XML.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_output( $sitemap ) {
|
||||
|
||||
$output = '<?xml version="1.0" encoding="' . esc_attr( $this->output_charset ) . '"?>';
|
||||
|
||||
if ( $this->stylesheet ) {
|
||||
/**
|
||||
* Filter the stylesheet URL for the XML sitemap.
|
||||
*
|
||||
* @param string $stylesheet Stylesheet URL.
|
||||
*/
|
||||
$output .= apply_filters( 'wpseo_stylesheet_url', $this->stylesheet ) . "\n";
|
||||
}
|
||||
|
||||
$output .= $sitemap;
|
||||
$output .= "\n<!-- XML Sitemap generated by Yoast SEO -->";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get charset for the output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_output_charset() {
|
||||
return $this->output_charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom stylesheet for this sitemap. Set to empty to just remove the default stylesheet.
|
||||
*
|
||||
* @param string $stylesheet Full XML-stylesheet declaration.
|
||||
*/
|
||||
public function set_stylesheet( $stylesheet ) {
|
||||
$this->stylesheet = $stylesheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the `<sitemap>` tag for a given URL.
|
||||
*
|
||||
* @param array $url Array of parts that make up this entry.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function sitemap_index_url( $url ) {
|
||||
|
||||
$date = null;
|
||||
|
||||
if ( ! empty( $url['lastmod'] ) ) {
|
||||
$date = YoastSEO()->helpers->date->format( $url['lastmod'] );
|
||||
}
|
||||
|
||||
$url['loc'] = htmlspecialchars( $url['loc'], ENT_COMPAT, $this->output_charset, false );
|
||||
|
||||
$output = "\t<sitemap>\n";
|
||||
$output .= "\t\t<loc>" . $url['loc'] . "</loc>\n";
|
||||
$output .= empty( $date ) ? '' : "\t\t<lastmod>" . htmlspecialchars( $date, ENT_COMPAT, $this->output_charset, false ) . "</lastmod>\n";
|
||||
$output .= "\t</sitemap>\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the `<url>` tag for a given URL.
|
||||
*
|
||||
* Public access for backwards compatibility reasons.
|
||||
*
|
||||
* @param array $url Array of parts that make up this entry.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sitemap_url( $url ) {
|
||||
|
||||
$date = null;
|
||||
|
||||
if ( ! empty( $url['mod'] ) ) {
|
||||
// Create a DateTime object date in the correct timezone.
|
||||
$date = YoastSEO()->helpers->date->format( $url['mod'] );
|
||||
}
|
||||
|
||||
$output = "\t<url>\n";
|
||||
$output .= "\t\t<loc>" . $this->encode_and_escape( $url['loc'] ) . "</loc>\n";
|
||||
$output .= empty( $date ) ? '' : "\t\t<lastmod>" . htmlspecialchars( $date, ENT_COMPAT, $this->output_charset, false ) . "</lastmod>\n";
|
||||
|
||||
if ( empty( $url['images'] ) ) {
|
||||
$url['images'] = [];
|
||||
}
|
||||
|
||||
foreach ( $url['images'] as $img ) {
|
||||
|
||||
if ( empty( $img['src'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$output .= "\t\t<image:image>\n";
|
||||
$output .= "\t\t\t<image:loc>" . $this->encode_and_escape( $img['src'] ) . "</image:loc>\n";
|
||||
$output .= "\t\t</image:image>\n";
|
||||
}
|
||||
unset( $img );
|
||||
|
||||
$output .= "\t</url>\n";
|
||||
|
||||
/**
|
||||
* Filters the output for the sitemap URL tag.
|
||||
*
|
||||
* @api string $output The output for the sitemap url tag.
|
||||
*
|
||||
* @param array $url The sitemap URL array on which the output is based.
|
||||
*/
|
||||
return apply_filters( 'wpseo_sitemap_url', $output, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the URL is encoded per RFC3986 and correctly escaped for use in an XML sitemap.
|
||||
*
|
||||
* This method works around a two quirks in esc_url():
|
||||
* 1. `esc_url()` leaves schema-relative URLs alone, while according to the sitemap specs,
|
||||
* the URL must always begin with a protocol.
|
||||
* 2. `esc_url()` escapes ampersands as `&` instead of the more common `&`.
|
||||
* According to the specs, `&` should be used, and even though this shouldn't
|
||||
* really make a difference in practice, to quote Jono: "I'd be nervous about &
|
||||
* given how many weird and wonderful things eat sitemaps", so better safe than sorry.
|
||||
*
|
||||
* @link https://www.sitemaps.org/protocol.html#xmlTagDefinitions
|
||||
* @link https://www.sitemaps.org/protocol.html#escaping
|
||||
* @link https://developer.wordpress.org/reference/functions/esc_url/
|
||||
*
|
||||
* @param string $url URL to encode and escape.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function encode_and_escape( $url ) {
|
||||
$url = $this->encode_url_rfc3986( $url );
|
||||
$url = esc_url( $url );
|
||||
$url = str_replace( '&', '&', $url );
|
||||
$url = str_replace( ''', ''', $url );
|
||||
|
||||
if ( strpos( $url, '//' ) === 0 ) {
|
||||
// Schema-relative URL for which esc_url() does not add a scheme.
|
||||
$url = 'http:' . $url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply some best effort conversion to comply with RFC3986.
|
||||
*
|
||||
* @param string $url URL to encode.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function encode_url_rfc3986( $url ) {
|
||||
|
||||
if ( filter_var( $url, FILTER_VALIDATE_URL ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$path = wp_parse_url( $url, PHP_URL_PATH );
|
||||
|
||||
if ( ! empty( $path ) && $path !== '/' ) {
|
||||
$encoded_path = explode( '/', $path );
|
||||
|
||||
// First decode the path, to prevent double encoding.
|
||||
$encoded_path = array_map( 'rawurldecode', $encoded_path );
|
||||
|
||||
$encoded_path = array_map( 'rawurlencode', $encoded_path );
|
||||
$encoded_path = implode( '/', $encoded_path );
|
||||
|
||||
$url = str_replace( $path, $encoded_path, $url );
|
||||
}
|
||||
|
||||
$query = wp_parse_url( $url, PHP_URL_QUERY );
|
||||
|
||||
if ( ! empty( $query ) ) {
|
||||
|
||||
parse_str( $query, $parsed_query );
|
||||
|
||||
$parsed_query = http_build_query( $parsed_query, '', '&', PHP_QUERY_RFC3986 );
|
||||
|
||||
$url = str_replace( $query, $parsed_query, $url );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the XSL URL that should be used in the current environment
|
||||
*
|
||||
* When home_url and site_url are not the same, the home_url should be used.
|
||||
* This is because the XSL needs to be served from the same domain, protocol and port
|
||||
* as the XML file that is loading it.
|
||||
*
|
||||
* @return string The XSL URL that needs to be used.
|
||||
*/
|
||||
protected function get_xsl_url() {
|
||||
if ( home_url() !== site_url() ) {
|
||||
return home_url( 'main-sitemap.xsl' );
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallback to circumvent a cross-domain security problem when the XLS file is
|
||||
* loaded from a different (sub)domain.
|
||||
*/
|
||||
if ( strpos( plugins_url(), home_url() ) !== 0 ) {
|
||||
return home_url( 'main-sitemap.xsl' );
|
||||
}
|
||||
|
||||
return plugin_dir_url( WPSEO_FILE ) . 'css/main-sitemap.xsl';
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Deactivating_Yoast_Seo_Conditional;
|
||||
|
||||
/**
|
||||
* Rewrite setup and handling for sitemaps functionality.
|
||||
*/
|
||||
class WPSEO_Sitemaps_Router {
|
||||
|
||||
/**
|
||||
* Sets up init logic.
|
||||
*/
|
||||
public function __construct() {
|
||||
// If we add rewrite rules during the plugin's deactivation, the flush_rewrite_rules that we perform afterwards won't properly flush those new rules.
|
||||
if ( YoastSEO()->classes->get( Deactivating_Yoast_Seo_Conditional::class )->is_met() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'init', [ $this, 'init' ], 1 );
|
||||
add_filter( 'redirect_canonical', [ $this, 'redirect_canonical' ] );
|
||||
add_action( 'template_redirect', [ $this, 'template_redirect' ], 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up rewrite rules.
|
||||
*/
|
||||
public function init() {
|
||||
global $wp;
|
||||
|
||||
$wp->add_query_var( 'sitemap' );
|
||||
$wp->add_query_var( 'sitemap_n' );
|
||||
$wp->add_query_var( 'yoast-sitemap-xsl' );
|
||||
|
||||
add_rewrite_rule( 'sitemap_index\.xml$', 'index.php?sitemap=1', 'top' );
|
||||
add_rewrite_rule( '([^/]+?)-sitemap([0-9]+)?\.xml$', 'index.php?sitemap=$matches[1]&sitemap_n=$matches[2]', 'top' );
|
||||
add_rewrite_rule( '([a-z]+)?-?sitemap\.xsl$', 'index.php?yoast-sitemap-xsl=$matches[1]', 'top' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop trailing slashes on sitemap.xml URLs.
|
||||
*
|
||||
* @param string $redirect The redirect URL currently determined.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function redirect_canonical( $redirect ) {
|
||||
|
||||
if ( get_query_var( 'sitemap' ) || get_query_var( 'yoast-sitemap-xsl' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects sitemap.xml to sitemap_index.xml.
|
||||
*/
|
||||
public function template_redirect() {
|
||||
if ( ! $this->needs_sitemap_index_redirect() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_safe_redirect( home_url( '/sitemap_index.xml' ), 301, 'Yoast SEO' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current request needs to be redirected to sitemap_index.xml.
|
||||
*
|
||||
* @global WP_Query $wp_query Current query.
|
||||
*
|
||||
* @return bool True if redirect is needed, false otherwise.
|
||||
*/
|
||||
public function needs_sitemap_index_redirect() {
|
||||
global $wp_query;
|
||||
|
||||
$protocol = 'http://';
|
||||
if ( ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on' ) {
|
||||
$protocol = 'https://';
|
||||
}
|
||||
|
||||
$domain = '';
|
||||
if ( isset( $_SERVER['SERVER_NAME'] ) ) {
|
||||
$domain = sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) );
|
||||
}
|
||||
|
||||
$path = '';
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
$path = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
}
|
||||
|
||||
// Due to different environment configurations, we need to check both SERVER_NAME and HTTP_HOST.
|
||||
$check_urls = [ $protocol . $domain . $path ];
|
||||
if ( ! empty( $_SERVER['HTTP_HOST'] ) ) {
|
||||
$check_urls[] = $protocol . sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) . $path;
|
||||
}
|
||||
|
||||
return $wp_query->is_404 && in_array( home_url( '/sitemap.xml' ), $check_urls, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create base URL for the sitemap.
|
||||
*
|
||||
* @param string $page Page to append to the base URL.
|
||||
*
|
||||
* @return string base URL (incl page)
|
||||
*/
|
||||
public static function get_base_url( $page ) {
|
||||
|
||||
global $wp_rewrite;
|
||||
|
||||
$base = $wp_rewrite->using_index_permalinks() ? 'index.php/' : '/';
|
||||
|
||||
/**
|
||||
* Filter the base URL of the sitemaps.
|
||||
*
|
||||
* @param string $base The string that should be added to home_url() to make the full base URL.
|
||||
*/
|
||||
$base = apply_filters( 'wpseo_sitemaps_base_url', $base );
|
||||
|
||||
/*
|
||||
* Get the scheme from the configured home URL instead of letting WordPress
|
||||
* determine the scheme based on the requested URI.
|
||||
*/
|
||||
return home_url( $base . $page, wp_parse_url( get_option( 'home' ), PHP_URL_SCHEME ) );
|
||||
}
|
||||
}
|
643
wp-content/plugins/wordpress-seo/inc/sitemaps/class-sitemaps.php
Normal file
643
wp-content/plugins/wordpress-seo/inc/sitemaps/class-sitemaps.php
Normal file
@ -0,0 +1,643 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Sitemaps.
|
||||
*
|
||||
* @todo This class could use a general description with some explanation on sitemaps. OR.
|
||||
*/
|
||||
class WPSEO_Sitemaps {
|
||||
|
||||
/**
|
||||
* Sitemap index identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SITEMAP_INDEX_TYPE = '1';
|
||||
|
||||
/**
|
||||
* Content of the sitemap to output.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sitemap = '';
|
||||
|
||||
/**
|
||||
* Flag to indicate if this is an invalid or empty sitemap.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $bad_sitemap = false;
|
||||
|
||||
/**
|
||||
* Whether or not the XML sitemap was served from a transient or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $transient = false;
|
||||
|
||||
/**
|
||||
* HTTP protocol to use in headers.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $http_protocol = 'HTTP/1.1';
|
||||
|
||||
/**
|
||||
* Holds the n variable.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $current_page = 1;
|
||||
|
||||
/**
|
||||
* The sitemaps router.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemaps_Router
|
||||
*/
|
||||
public $router;
|
||||
|
||||
/**
|
||||
* The sitemap renderer.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemaps_Renderer
|
||||
*/
|
||||
public $renderer;
|
||||
|
||||
/**
|
||||
* The sitemap cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemaps_Cache
|
||||
*/
|
||||
public $cache;
|
||||
|
||||
/**
|
||||
* The sitemap providers.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemap_Provider[]
|
||||
*/
|
||||
public $providers;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'after_setup_theme', [ $this, 'init_sitemaps_providers' ] );
|
||||
add_action( 'after_setup_theme', [ $this, 'reduce_query_load' ], 99 );
|
||||
add_action( 'pre_get_posts', [ $this, 'redirect' ], 1 );
|
||||
add_action( 'wpseo_hit_sitemap_index', [ $this, 'hit_sitemap_index' ] );
|
||||
|
||||
$this->router = new WPSEO_Sitemaps_Router();
|
||||
$this->renderer = new WPSEO_Sitemaps_Renderer();
|
||||
$this->cache = new WPSEO_Sitemaps_Cache();
|
||||
|
||||
if ( ! empty( $_SERVER['SERVER_PROTOCOL'] ) ) {
|
||||
$this->http_protocol = sanitize_text_field( wp_unslash( $_SERVER['SERVER_PROTOCOL'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize sitemap providers classes.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
public function init_sitemaps_providers() {
|
||||
|
||||
$this->providers = [
|
||||
new WPSEO_Post_Type_Sitemap_Provider(),
|
||||
new WPSEO_Taxonomy_Sitemap_Provider(),
|
||||
new WPSEO_Author_Sitemap_Provider(),
|
||||
];
|
||||
|
||||
$external_providers = apply_filters( 'wpseo_sitemaps_providers', [] );
|
||||
|
||||
foreach ( $external_providers as $provider ) {
|
||||
if ( is_object( $provider ) && $provider instanceof WPSEO_Sitemap_Provider ) {
|
||||
$this->providers[] = $provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the current request URI, if we can determine it's probably an XML sitemap, kill loading the widgets.
|
||||
*/
|
||||
public function reduce_query_load() {
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
return;
|
||||
}
|
||||
$request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
$extension = substr( $request_uri, -4 );
|
||||
if ( stripos( $request_uri, 'sitemap' ) !== false && in_array( $extension, [ '.xml', '.xsl' ], true ) ) {
|
||||
remove_all_actions( 'widgets_init' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register your own sitemap. Call this during 'init'.
|
||||
*
|
||||
* @param string $name The name of the sitemap.
|
||||
* @param callback $building_function Function to build your sitemap.
|
||||
* @param string $rewrite Optional. Regular expression to match your sitemap with.
|
||||
*/
|
||||
public function register_sitemap( $name, $building_function, $rewrite = '' ) {
|
||||
add_action( 'wpseo_do_sitemap_' . $name, $building_function );
|
||||
if ( ! empty( $rewrite ) ) {
|
||||
add_rewrite_rule( $rewrite, 'index.php?sitemap=' . $name, 'top' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register your own XSL file. Call this during 'init'.
|
||||
*
|
||||
* @since 1.4.23
|
||||
*
|
||||
* @param string $name The name of the XSL file.
|
||||
* @param callback $building_function Function to build your XSL file.
|
||||
* @param string $rewrite Optional. Regular expression to match your sitemap with.
|
||||
*/
|
||||
public function register_xsl( $name, $building_function, $rewrite = '' ) {
|
||||
add_action( 'wpseo_xsl_' . $name, $building_function );
|
||||
if ( ! empty( $rewrite ) ) {
|
||||
add_rewrite_rule( $rewrite, 'index.php?yoast-sitemap-xsl=' . $name, 'top' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sitemap current page to allow creating partial sitemaps with WP-CLI
|
||||
* in a one-off process.
|
||||
*
|
||||
* @param int $current_page The part that should be generated.
|
||||
*/
|
||||
public function set_n( $current_page ) {
|
||||
if ( is_scalar( $current_page ) && intval( $current_page ) > 0 ) {
|
||||
$this->current_page = intval( $current_page );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sitemap content to display after you have generated it.
|
||||
*
|
||||
* @param string $sitemap The generated sitemap to output.
|
||||
*/
|
||||
public function set_sitemap( $sitemap ) {
|
||||
$this->sitemap = $sitemap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set as true to make the request 404. Used stop the display of empty sitemaps or invalid requests.
|
||||
*
|
||||
* @param bool $is_bad Is this a bad request. True or false.
|
||||
*/
|
||||
public function set_bad_sitemap( $is_bad ) {
|
||||
$this->bad_sitemap = (bool) $is_bad;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent stupid plugins from running shutdown scripts when we're obviously not outputting HTML.
|
||||
*
|
||||
* @since 1.4.16
|
||||
*/
|
||||
public function sitemap_close() {
|
||||
remove_all_actions( 'wp_footer' );
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hijack requests for potential sitemaps and XSL files.
|
||||
*
|
||||
* @param \WP_Query $query Main query instance.
|
||||
*/
|
||||
public function redirect( $query ) {
|
||||
|
||||
if ( ! $query->is_main_query() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$yoast_sitemap_xsl = get_query_var( 'yoast-sitemap-xsl' );
|
||||
|
||||
if ( ! empty( $yoast_sitemap_xsl ) ) {
|
||||
/*
|
||||
* This is a method to provide the XSL via the home_url.
|
||||
* Needed when the site_url and home_url are not the same.
|
||||
* Loading the XSL needs to come from the same domain, protocol and port as the XML.
|
||||
*
|
||||
* Whenever home_url and site_url are the same, the file can be loaded directly.
|
||||
*/
|
||||
$this->xsl_output( $yoast_sitemap_xsl );
|
||||
$this->sitemap_close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$type = get_query_var( 'sitemap' );
|
||||
|
||||
if ( empty( $type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( get_query_var( 'sitemap_n' ) === '1' || get_query_var( 'sitemap_n' ) === '0' ) {
|
||||
wp_safe_redirect( home_url( "/$type-sitemap.xml" ), 301, 'Yoast SEO' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->set_n( get_query_var( 'sitemap_n' ) );
|
||||
|
||||
if ( ! $this->get_sitemap_from_cache( $type, $this->current_page ) ) {
|
||||
$this->build_sitemap( $type );
|
||||
}
|
||||
|
||||
if ( $this->bad_sitemap ) {
|
||||
$query->set_404();
|
||||
status_header( 404 );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->output();
|
||||
$this->sitemap_close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get the sitemap from cache.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page_number The page number to retrieve.
|
||||
*
|
||||
* @return bool If the sitemap has been retrieved from cache.
|
||||
*/
|
||||
private function get_sitemap_from_cache( $type, $page_number ) {
|
||||
|
||||
$this->transient = false;
|
||||
|
||||
if ( $this->cache->is_enabled() !== true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires before the attempt to retrieve XML sitemap from the transient cache.
|
||||
*
|
||||
* @param WPSEO_Sitemaps $sitemaps Sitemaps object.
|
||||
*/
|
||||
do_action( 'wpseo_sitemap_stylesheet_cache_' . $type, $this );
|
||||
|
||||
$sitemap_cache_data = $this->cache->get_sitemap_data( $type, $page_number );
|
||||
|
||||
// No cache was found, refresh it because cache is enabled.
|
||||
if ( empty( $sitemap_cache_data ) ) {
|
||||
return $this->refresh_sitemap_cache( $type, $page_number );
|
||||
}
|
||||
|
||||
// Cache object was found, parse information.
|
||||
$this->transient = true;
|
||||
|
||||
$this->sitemap = $sitemap_cache_data->get_sitemap();
|
||||
$this->bad_sitemap = ! $sitemap_cache_data->is_usable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and save sitemap to cache.
|
||||
*
|
||||
* @param string $type Sitemap type.
|
||||
* @param int $page_number The page number to save to.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function refresh_sitemap_cache( $type, $page_number ) {
|
||||
$this->set_n( $page_number );
|
||||
$this->build_sitemap( $type );
|
||||
|
||||
return $this->cache->store_sitemap( $type, $page_number, $this->sitemap, ! $this->bad_sitemap );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to build the requested sitemap.
|
||||
*
|
||||
* Sets $bad_sitemap if this isn't for the root sitemap, a post type or taxonomy.
|
||||
*
|
||||
* @param string $type The requested sitemap's identifier.
|
||||
*/
|
||||
public function build_sitemap( $type ) {
|
||||
|
||||
/**
|
||||
* Filter the type of sitemap to build.
|
||||
*
|
||||
* @param string $type Sitemap type, determined by the request.
|
||||
*/
|
||||
$type = apply_filters( 'wpseo_build_sitemap_post_type', $type );
|
||||
|
||||
if ( $type === '1' ) {
|
||||
$this->build_root_map();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$entries_per_page = $this->get_entries_per_page();
|
||||
|
||||
foreach ( $this->providers as $provider ) {
|
||||
if ( ! $provider->handles_type( $type ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$links = $provider->get_sitemap_links( $type, $entries_per_page, $this->current_page );
|
||||
} catch ( OutOfBoundsException $exception ) {
|
||||
$this->bad_sitemap = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sitemap = $this->renderer->get_sitemap( $links, $type, $this->current_page );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( has_action( 'wpseo_do_sitemap_' . $type ) ) {
|
||||
/**
|
||||
* Fires custom handler, if hooked to generate sitemap for the type.
|
||||
*/
|
||||
do_action( 'wpseo_do_sitemap_' . $type );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->bad_sitemap = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the root sitemap (example.com/sitemap_index.xml) which lists sub-sitemaps for other content types.
|
||||
*/
|
||||
public function build_root_map() {
|
||||
|
||||
$links = [];
|
||||
$entries_per_page = $this->get_entries_per_page();
|
||||
|
||||
foreach ( $this->providers as $provider ) {
|
||||
$links = array_merge( $links, $provider->get_index_links( $entries_per_page ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the sitemap links array before the index sitemap is built.
|
||||
*
|
||||
* @param array $links Array of sitemap links
|
||||
*/
|
||||
$links = apply_filters( 'wpseo_sitemap_index_links', $links );
|
||||
|
||||
if ( empty( $links ) ) {
|
||||
$this->bad_sitemap = true;
|
||||
$this->sitemap = '';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sitemap = $this->renderer->get_index( $links );
|
||||
}
|
||||
|
||||
/**
|
||||
* Spits out the XSL for the XML sitemap.
|
||||
*
|
||||
* @since 1.4.13
|
||||
*
|
||||
* @param string $type Type to output.
|
||||
*/
|
||||
public function xsl_output( $type ) {
|
||||
|
||||
if ( $type !== 'main' ) {
|
||||
|
||||
/**
|
||||
* Fires for the output of XSL for XML sitemaps, other than type "main".
|
||||
*/
|
||||
do_action( 'wpseo_xsl_' . $type );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
header( $this->http_protocol . ' 200 OK', true, 200 );
|
||||
// Prevent the search engines from indexing the XML Sitemap.
|
||||
header( 'X-Robots-Tag: noindex, follow', true );
|
||||
header( 'Content-Type: text/xml' );
|
||||
|
||||
// Make the browser cache this file properly.
|
||||
$expires = YEAR_IN_SECONDS;
|
||||
header( 'Pragma: public' );
|
||||
header( 'Cache-Control: max-age=' . $expires );
|
||||
header( 'Expires: ' . YoastSEO()->helpers->date->format_timestamp( ( time() + $expires ), 'D, d M Y H:i:s' ) . ' GMT' );
|
||||
|
||||
// Don't use WP_Filesystem() here because that's not initialized yet. See https://yoast.atlassian.net/browse/QAK-2043.
|
||||
readfile( WPSEO_PATH . 'css/main-sitemap.xsl' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Spit out the generated sitemap.
|
||||
*/
|
||||
public function output() {
|
||||
$this->send_headers();
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaping sitemap as either xml or html results in empty document.
|
||||
echo $this->renderer->get_output( $this->sitemap );
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request to the sitemap index to cache it before the arrival of the search engines.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function hit_sitemap_index() {
|
||||
if ( ! $this->cache->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_remote_get( WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GMT modification date for the last modified post in the post type.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* @param string|array $post_types Post type or array of types.
|
||||
* @param bool $return_all Flag to return array of values.
|
||||
*
|
||||
* @return string|array|false
|
||||
*/
|
||||
public static function get_last_modified_gmt( $post_types, $return_all = false ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
static $post_type_dates = null;
|
||||
|
||||
if ( ! is_array( $post_types ) ) {
|
||||
$post_types = [ $post_types ];
|
||||
}
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
if ( ! isset( $post_type_dates[ $post_type ] ) ) { // If we hadn't seen post type before. R.
|
||||
$post_type_dates = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_null( $post_type_dates ) ) {
|
||||
|
||||
$post_type_dates = [];
|
||||
$post_type_names = WPSEO_Post_Type::get_accessible_post_types();
|
||||
|
||||
if ( ! empty( $post_type_names ) ) {
|
||||
$post_statuses = array_map( 'esc_sql', self::get_post_statuses() );
|
||||
|
||||
$sql = "
|
||||
SELECT post_type, MAX(post_modified_gmt) AS date
|
||||
FROM $wpdb->posts
|
||||
WHERE post_status IN ('" . implode( "','", $post_statuses ) . "')
|
||||
AND post_type IN ('" . implode( "','", $post_type_names ) . "')
|
||||
GROUP BY post_type
|
||||
ORDER BY date DESC
|
||||
";
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- They are prepared on the lines above and a direct query is required.
|
||||
foreach ( $wpdb->get_results( $sql ) as $obj ) {
|
||||
$post_type_dates[ $obj->post_type ] = $obj->date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dates = array_intersect_key( $post_type_dates, array_flip( $post_types ) );
|
||||
|
||||
if ( count( $dates ) > 0 ) {
|
||||
if ( $return_all ) {
|
||||
return $dates;
|
||||
}
|
||||
|
||||
return max( $dates );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the modification date for the last modified post in the post type.
|
||||
*
|
||||
* @param array $post_types Post types to get the last modification date for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_last_modified( $post_types ) {
|
||||
return YoastSEO()->helpers->date->format( self::get_last_modified_gmt( $post_types ) );
|
||||
}
|
||||
|
||||
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Argument is kept for documentation purposes.
|
||||
|
||||
/**
|
||||
* Notify search engines of the updated sitemap.
|
||||
*
|
||||
* @deprecated 19.2
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string|null $url Optional URL to make the ping for.
|
||||
*/
|
||||
public static function ping_search_engines( $url = null ) {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 19.2', 'WPSEO_Sitemaps_Admin::ping_search_engines' );
|
||||
|
||||
$admin = new WPSEO_Sitemaps_Admin();
|
||||
$admin->ping_search_engines();
|
||||
}
|
||||
|
||||
// phpcs:enable
|
||||
|
||||
/**
|
||||
* Get the maximum number of entries per XML sitemap.
|
||||
*
|
||||
* @return int The maximum number of entries.
|
||||
*/
|
||||
protected function get_entries_per_page() {
|
||||
/**
|
||||
* Filter the maximum number of entries per XML sitemap.
|
||||
*
|
||||
* After changing the output of the filter, make sure that you disable and enable the
|
||||
* sitemaps to make sure the value is picked up for the sitemap cache.
|
||||
*
|
||||
* @param int $entries The maximum number of entries per XML sitemap.
|
||||
*/
|
||||
$entries = (int) apply_filters( 'wpseo_sitemap_entries_per_page', 1000 );
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post statuses for post_type or the root sitemap.
|
||||
*
|
||||
* @since 10.2
|
||||
*
|
||||
* @param string $type Provide a type for a post_type sitemap, SITEMAP_INDEX_TYPE for the root sitemap.
|
||||
*
|
||||
* @return array List of post statuses.
|
||||
*/
|
||||
public static function get_post_statuses( $type = self::SITEMAP_INDEX_TYPE ) {
|
||||
/**
|
||||
* Filter post status list for sitemap query for the post type.
|
||||
*
|
||||
* @param array $post_statuses Post status list, defaults to array( 'publish' ).
|
||||
* @param string $type Post type or SITEMAP_INDEX_TYPE.
|
||||
*/
|
||||
$post_statuses = apply_filters( 'wpseo_sitemap_post_statuses', [ 'publish' ], $type );
|
||||
|
||||
if ( ! is_array( $post_statuses ) || empty( $post_statuses ) ) {
|
||||
$post_statuses = [ 'publish' ];
|
||||
}
|
||||
|
||||
if ( ( $type === self::SITEMAP_INDEX_TYPE || $type === 'attachment' )
|
||||
&& ! in_array( 'inherit', $post_statuses, true )
|
||||
) {
|
||||
$post_statuses[] = 'inherit';
|
||||
}
|
||||
|
||||
return $post_statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends all the required HTTP Headers.
|
||||
*/
|
||||
private function send_headers() {
|
||||
if ( headers_sent() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = [
|
||||
$this->http_protocol . ' 200 OK' => 200,
|
||||
// Prevent the search engines from indexing the XML Sitemap.
|
||||
'X-Robots-Tag: noindex, follow' => '',
|
||||
'Content-Type: text/xml; charset=' . esc_attr( $this->renderer->get_output_charset() ) => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter the HTTP headers we send before an XML sitemap.
|
||||
*
|
||||
* @param array $headers The HTTP headers we're going to send out.
|
||||
*/
|
||||
$headers = apply_filters( 'wpseo_sitemap_http_headers', $headers );
|
||||
|
||||
foreach ( $headers as $header => $status ) {
|
||||
if ( is_numeric( $status ) ) {
|
||||
header( $header, true, $status );
|
||||
continue;
|
||||
}
|
||||
header( $header, true );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,323 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap provider for author archives.
|
||||
*/
|
||||
class WPSEO_Taxonomy_Sitemap_Provider implements WPSEO_Sitemap_Provider {
|
||||
|
||||
/**
|
||||
* Holds image parser instance.
|
||||
*
|
||||
* @var WPSEO_Sitemap_Image_Parser
|
||||
*/
|
||||
protected static $image_parser;
|
||||
|
||||
/**
|
||||
* Determines whether images should be included in the XML sitemap.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $include_images;
|
||||
|
||||
/**
|
||||
* Set up object properties for data reuse.
|
||||
*/
|
||||
public function __construct() {
|
||||
/**
|
||||
* Filter - Allows excluding images from the XML sitemap.
|
||||
*
|
||||
* @param bool $include True to include, false to exclude.
|
||||
*/
|
||||
$this->include_images = apply_filters( 'wpseo_xml_sitemap_include_images', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handles_type( $type ) {
|
||||
|
||||
$taxonomy = get_taxonomy( $type );
|
||||
|
||||
if ( $taxonomy === false || ! $this->is_valid_taxonomy( $taxonomy->name ) || ! $taxonomy->public ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the links for the sitemap.
|
||||
*
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries ) {
|
||||
|
||||
$taxonomies = get_taxonomies( [ 'public' => true ], 'objects' );
|
||||
|
||||
if ( empty( $taxonomies ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$taxonomy_names = array_filter( array_keys( $taxonomies ), [ $this, 'is_valid_taxonomy' ] );
|
||||
$taxonomies = array_intersect_key( $taxonomies, array_flip( $taxonomy_names ) );
|
||||
|
||||
// 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 bool $exclude Defaults to true.
|
||||
* @param array $taxonomy_names Array of names for the taxonomies being processed.
|
||||
*/
|
||||
$hide_empty = apply_filters( 'wpseo_sitemap_exclude_empty_terms', true, $taxonomy_names );
|
||||
|
||||
$all_taxonomies = [];
|
||||
|
||||
foreach ( $taxonomy_names as $taxonomy_name ) {
|
||||
/**
|
||||
* Filter the setting of excluding empty terms from the XML sitemap for a specific taxonomy.
|
||||
*
|
||||
* @param bool $exclude Defaults to the sitewide setting.
|
||||
* @param string $taxonomy_name The name of the taxonomy being processed.
|
||||
*/
|
||||
$hide_empty_tax = apply_filters( 'wpseo_sitemap_exclude_empty_terms_taxonomy', $hide_empty, $taxonomy_name );
|
||||
|
||||
$term_args = [
|
||||
'hide_empty' => $hide_empty_tax,
|
||||
'fields' => 'ids',
|
||||
];
|
||||
$taxonomy_terms = get_terms( $taxonomy_name, $term_args );
|
||||
|
||||
if ( count( $taxonomy_terms ) > 0 ) {
|
||||
$all_taxonomies[ $taxonomy_name ] = $taxonomy_terms;
|
||||
}
|
||||
}
|
||||
|
||||
$index = [];
|
||||
|
||||
foreach ( $taxonomies as $tax_name => $tax ) {
|
||||
|
||||
if ( ! isset( $all_taxonomies[ $tax_name ] ) ) { // No eligible terms found.
|
||||
continue;
|
||||
}
|
||||
|
||||
$total_count = ( isset( $all_taxonomies[ $tax_name ] ) ) ? count( $all_taxonomies[ $tax_name ] ) : 1;
|
||||
$max_pages = 1;
|
||||
|
||||
if ( $total_count > $max_entries ) {
|
||||
$max_pages = (int) ceil( $total_count / $max_entries );
|
||||
}
|
||||
|
||||
$last_modified_gmt = WPSEO_Sitemaps::get_last_modified_gmt( $tax->object_type );
|
||||
|
||||
for ( $page_counter = 0; $page_counter < $max_pages; $page_counter++ ) {
|
||||
|
||||
$current_page = ( $page_counter === 0 ) ? '' : ( $page_counter + 1 );
|
||||
|
||||
if ( ! is_array( $tax->object_type ) || count( $tax->object_type ) === 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$terms = array_splice( $all_taxonomies[ $tax_name ], 0, $max_entries );
|
||||
|
||||
if ( ! $terms ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$args = [
|
||||
'post_type' => $tax->object_type,
|
||||
'tax_query' => [
|
||||
[
|
||||
'taxonomy' => $tax_name,
|
||||
'terms' => $terms,
|
||||
],
|
||||
],
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
'posts_per_page' => 1,
|
||||
];
|
||||
$query = new WP_Query( $args );
|
||||
|
||||
if ( $query->have_posts() ) {
|
||||
$date = $query->posts[0]->post_modified_gmt;
|
||||
}
|
||||
else {
|
||||
$date = $last_modified_gmt;
|
||||
}
|
||||
|
||||
$index[] = [
|
||||
'loc' => WPSEO_Sitemaps_Router::get_base_url( $tax_name . '-sitemap' . $current_page . '.xml' ),
|
||||
'lastmod' => $date,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @throws OutOfBoundsException When an invalid page is requested.
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page ) {
|
||||
global $wpdb;
|
||||
|
||||
$links = [];
|
||||
if ( ! $this->handles_type( $type ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$taxonomy = get_taxonomy( $type );
|
||||
|
||||
$steps = $max_entries;
|
||||
$offset = ( $current_page > 1 ) ? ( ( $current_page - 1 ) * $max_entries ) : 0;
|
||||
|
||||
/** This filter is documented in inc/sitemaps/class-taxonomy-sitemap-provider.php */
|
||||
$hide_empty = apply_filters( 'wpseo_sitemap_exclude_empty_terms', true, [ $taxonomy->name ] );
|
||||
/** This filter is documented in inc/sitemaps/class-taxonomy-sitemap-provider.php */
|
||||
$hide_empty_tax = apply_filters( 'wpseo_sitemap_exclude_empty_terms_taxonomy', $hide_empty, $taxonomy->name );
|
||||
$terms = get_terms(
|
||||
[
|
||||
'taxonomy' => $taxonomy->name,
|
||||
'hide_empty' => $hide_empty_tax,
|
||||
'update_term_meta_cache' => false,
|
||||
'offset' => $offset,
|
||||
'number' => $steps,
|
||||
]
|
||||
);
|
||||
|
||||
// If there are no terms fetched for this range, we are on an invalid page.
|
||||
if ( empty( $terms ) ) {
|
||||
throw new OutOfBoundsException( 'Invalid sitemap page requested' );
|
||||
}
|
||||
|
||||
$post_statuses = array_map( 'esc_sql', WPSEO_Sitemaps::get_post_statuses() );
|
||||
|
||||
// Grab last modified date.
|
||||
$sql = "
|
||||
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 ('" . implode( "','", $post_statuses ) . "')
|
||||
AND p.post_password = ''
|
||||
";
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_exclude_from_sitemap_by_term_ids' - Allow excluding terms by ID.
|
||||
*
|
||||
* @api array $terms_to_exclude The terms to exclude.
|
||||
*/
|
||||
$terms_to_exclude = apply_filters( 'wpseo_exclude_from_sitemap_by_term_ids', [] );
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
|
||||
if ( in_array( $term->term_id, $terms_to_exclude, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = [];
|
||||
|
||||
$tax_noindex = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'noindex' );
|
||||
|
||||
if ( $tax_noindex === 'noindex' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$canonical = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'canonical' );
|
||||
$url['loc'] = get_term_link( $term, $term->taxonomy );
|
||||
|
||||
if ( is_string( $canonical ) && $canonical !== '' && $canonical !== $url['loc'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url['mod'] = $wpdb->get_var( $wpdb->prepare( $sql, $term->taxonomy, $term->term_id ) );
|
||||
|
||||
if ( $this->include_images ) {
|
||||
$url['images'] = $this->get_image_parser()->get_term_images( $term );
|
||||
}
|
||||
|
||||
// Deprecated, kept for backwards data compat. R.
|
||||
$url['chf'] = 'daily';
|
||||
$url['pri'] = 1;
|
||||
|
||||
/** This filter is documented at inc/sitemaps/class-post-type-sitemap-provider.php */
|
||||
$url = apply_filters( 'wpseo_sitemap_entry', $url, 'term', $term );
|
||||
|
||||
if ( ! empty( $url ) ) {
|
||||
$links[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if taxonomy by name is valid to appear in sitemaps.
|
||||
*
|
||||
* @param string $taxonomy_name Taxonomy name to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_taxonomy( $taxonomy_name ) {
|
||||
|
||||
if ( WPSEO_Options::get( "noindex-tax-{$taxonomy_name}" ) === true ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( in_array( $taxonomy_name, [ 'link_category', 'nav_menu' ], true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $taxonomy_name === 'post_format' && WPSEO_Options::get( 'disable-post_format', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to exclude the taxonomy from the XML sitemap.
|
||||
*
|
||||
* @param bool $exclude Defaults to false.
|
||||
* @param string $taxonomy_name Name of the taxonomy to exclude..
|
||||
*/
|
||||
if ( apply_filters( 'wpseo_sitemap_exclude_taxonomy', false, $taxonomy_name ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Image Parser.
|
||||
*
|
||||
* @return WPSEO_Sitemap_Image_Parser
|
||||
*/
|
||||
protected function get_image_parser() {
|
||||
if ( ! isset( self::$image_parser ) ) {
|
||||
self::$image_parser = new WPSEO_Sitemap_Image_Parser();
|
||||
}
|
||||
|
||||
return self::$image_parser;
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cache Data interface.
|
||||
*/
|
||||
interface WPSEO_Sitemap_Cache_Data_Interface {
|
||||
|
||||
/**
|
||||
* Status for normal, usable sitemap.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const OK = 'ok';
|
||||
|
||||
/**
|
||||
* Status for unusable sitemap.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ERROR = 'error';
|
||||
|
||||
/**
|
||||
* Status for unusable sitemap because it cannot be identified.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const UNKNOWN = 'unknown';
|
||||
|
||||
/**
|
||||
* Set the content of the sitemap.
|
||||
*
|
||||
* @param string $sitemap The XML content of the sitemap.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_sitemap( $sitemap );
|
||||
|
||||
/**
|
||||
* Set the status of the sitemap.
|
||||
*
|
||||
* @param bool|string $usable True/False or 'ok'/'error' for status.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_status( $usable );
|
||||
|
||||
/**
|
||||
* Builds the sitemap.
|
||||
*
|
||||
* @return string The XML content of the sitemap.
|
||||
*/
|
||||
public function get_sitemap();
|
||||
|
||||
/**
|
||||
* Get the status of this sitemap.
|
||||
*
|
||||
* @return string Status 'ok', 'error' or 'unknown'.
|
||||
*/
|
||||
public function get_status();
|
||||
|
||||
/**
|
||||
* Is the sitemap content usable ?
|
||||
*
|
||||
* @return bool True if the sitemap is usable, False if not.
|
||||
*/
|
||||
public function is_usable();
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap Provider interface.
|
||||
*/
|
||||
interface WPSEO_Sitemap_Provider {
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handles_type( $type );
|
||||
|
||||
/**
|
||||
* Get set of sitemaps index link data.
|
||||
*
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries );
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
Reference in New Issue
Block a user