PDF rausgenommen
This commit is contained in:
@ -0,0 +1,235 @@
|
||||
<?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 boolean
|
||||
*/
|
||||
public function handles_type( $type ) {
|
||||
|
||||
return $type === 'author';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries ) {
|
||||
|
||||
if ( WPSEO_Options::get( 'disable-author', false ) || WPSEO_Options::get( 'noindex-author-wpseo', false ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// @todo Consider doing this less often / when necessary. R.
|
||||
$this->update_user_meta();
|
||||
|
||||
$has_exclude_filter = has_filter( 'wpseo_sitemap_exclude_author' );
|
||||
|
||||
$query_arguments = array();
|
||||
|
||||
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 array();
|
||||
}
|
||||
|
||||
$index = array();
|
||||
$page = 1;
|
||||
$user_pages = array_chunk( $users, $max_entries );
|
||||
|
||||
if ( count( $user_pages ) === 1 ) {
|
||||
$page = '';
|
||||
}
|
||||
|
||||
foreach ( $user_pages as $users_page ) {
|
||||
|
||||
$user_id = array_shift( $users_page ); // Time descending, first user on page is most recently updated.
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
$index[] = array(
|
||||
'loc' => WPSEO_Sitemaps_Router::get_base_url( 'author-sitemap' . $page . '.xml' ),
|
||||
'lastmod' => '@' . $user->_yoast_wpseo_profile_updated, // @ for explicit timestamp format
|
||||
);
|
||||
|
||||
$page++;
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve users, taking account of all necessary exclusions.
|
||||
*
|
||||
* @param array $arguments Arguments to add.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_users( $arguments = array() ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$defaults = array(
|
||||
// @todo Re-enable after plugin requirements raised to WP 4.6 with the fix.
|
||||
// 'who' => 'authors', Breaks meta keys, {@link https://core.trac.wordpress.org/ticket/36724#ticket} R.
|
||||
'meta_key' => '_yoast_wpseo_profile_updated',
|
||||
'orderby' => 'meta_value_num',
|
||||
'order' => 'DESC',
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => $wpdb->get_blog_prefix() . 'user_level',
|
||||
'value' => '0',
|
||||
'compare' => '!=',
|
||||
),
|
||||
array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => 'wpseo_noindex_author',
|
||||
'value' => 'on',
|
||||
'compare' => '!=',
|
||||
),
|
||||
array(
|
||||
'key' => 'wpseo_noindex_author',
|
||||
'compare' => 'NOT EXISTS',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if ( WPSEO_Options::get( 'noindex-author-noposts-wpseo', true ) ) {
|
||||
// $defaults['who'] = ''; // Otherwise it cancels out next argument.
|
||||
$defaults['has_published_posts'] = true;
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page ) {
|
||||
|
||||
$links = array();
|
||||
|
||||
if ( WPSEO_Options::get( 'disable-author', false ) || WPSEO_Options::get( 'noindex-author-wpseo', false ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$user_criteria = array(
|
||||
'offset' => ( $current_page - 1 ) * $max_entries,
|
||||
'number' => $max_entries,
|
||||
);
|
||||
$users = $this->get_users( $user_criteria );
|
||||
$users = $this->exclude_users( $users );
|
||||
|
||||
if ( empty( $users ) ) {
|
||||
$users = array();
|
||||
}
|
||||
|
||||
$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 = array(
|
||||
'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 = array(
|
||||
'who' => 'authors',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'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,673 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap provider for author archives.
|
||||
*/
|
||||
class WPSEO_Post_Type_Sitemap_Provider implements WPSEO_Sitemap_Provider {
|
||||
|
||||
/**
|
||||
* Holds the home_url() value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $home_url;
|
||||
|
||||
/**
|
||||
* Holds image parser instance.
|
||||
*
|
||||
* @var WPSEO_Sitemap_Image_Parser
|
||||
*/
|
||||
protected static $image_parser;
|
||||
|
||||
/**
|
||||
* Holds instance of classifier for a link.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected static $classifier;
|
||||
|
||||
/**
|
||||
* Static front page ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $page_on_front_id;
|
||||
|
||||
/**
|
||||
* Posts page ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $page_for_posts_id;
|
||||
|
||||
/**
|
||||
* Set up object properties for data reuse.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'save_post', array( $this, 'save_post' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get front page ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_page_on_front_id() {
|
||||
if ( ! isset( self::$page_on_front_id ) ) {
|
||||
self::$page_on_front_id = (int) get_option( 'page_on_front' );
|
||||
}
|
||||
|
||||
return self::$page_on_front_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page for posts ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_page_for_posts_id() {
|
||||
if ( ! isset( self::$page_for_posts_id ) ) {
|
||||
self::$page_for_posts_id = (int) get_option( 'page_for_posts' );
|
||||
}
|
||||
|
||||
return self::$page_for_posts_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Classifier for a link.
|
||||
*
|
||||
* @return WPSEO_Link_Type_Classifier
|
||||
*/
|
||||
protected function get_classifier() {
|
||||
if ( ! isset( self::$classifier ) ) {
|
||||
self::$classifier = new WPSEO_Link_Type_Classifier( $this->get_home_url() );
|
||||
}
|
||||
|
||||
return self::$classifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Home URL.
|
||||
*
|
||||
* This has been moved from the constructor because wp_rewrite is not available on plugins_loaded in multisite.
|
||||
* It will now be requested on need and not on initialization.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_home_url() {
|
||||
if ( ! isset( self::$home_url ) ) {
|
||||
self::$home_url = WPSEO_Utils::home_url();
|
||||
}
|
||||
|
||||
return self::$home_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function handles_type( $type ) {
|
||||
|
||||
return post_type_exists( $type );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries ) {
|
||||
global $wpdb;
|
||||
|
||||
// Consider using WPSEO_Post_Type::get_accessible_post_types() to filter out any `no-index` post-types.
|
||||
$post_types = WPSEO_Post_Type::get_accessible_post_types();
|
||||
$post_types = array_filter( $post_types, array( $this, 'is_valid_post_type' ) );
|
||||
$last_modified_times = WPSEO_Sitemaps::get_last_modified_gmt( $post_types, true );
|
||||
$index = array();
|
||||
|
||||
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 = array();
|
||||
|
||||
if ( $max_pages > 1 ) {
|
||||
|
||||
$sql = "
|
||||
SELECT post_modified_gmt
|
||||
FROM ( SELECT @rownum:=0 ) init
|
||||
JOIN {$wpdb->posts} USE INDEX( type_status_date )
|
||||
WHERE post_status IN ( 'publish', 'inherit' )
|
||||
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 = ( $max_pages > 1 ) ? ( $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[] = array(
|
||||
'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
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page ) {
|
||||
|
||||
$links = array();
|
||||
$post_type = $type;
|
||||
|
||||
if ( ! $this->is_valid_post_type( $post_type ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$steps = min( 100, $max_entries );
|
||||
$offset = ( $current_page > 1 ) ? ( ( $current_page - 1 ) * $max_entries ) : 0;
|
||||
$total = ( $offset + $max_entries );
|
||||
|
||||
$typecount = $this->get_post_type_count( $post_type );
|
||||
|
||||
if ( $total > $typecount ) {
|
||||
$total = $typecount;
|
||||
}
|
||||
|
||||
if ( $current_page === 1 ) {
|
||||
$links = array_merge( $links, $this->get_first_links( $post_type ) );
|
||||
}
|
||||
|
||||
if ( $typecount === 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 = array();
|
||||
|
||||
if ( $post_type === 'page' && $this->get_page_for_posts_id() ) {
|
||||
$excluded_posts_ids[] = $this->get_page_for_posts_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 = array();
|
||||
}
|
||||
|
||||
$excluded_posts_ids = array_map( 'intval', $excluded_posts_ids );
|
||||
|
||||
if ( $post_type === 'page' && $this->get_page_on_front_id() ) {
|
||||
$excluded_posts_ids[] = $this->get_page_on_front_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 = array();
|
||||
$archive_url = false;
|
||||
|
||||
if ( $post_type === 'page' ) {
|
||||
|
||||
if ( $this->get_page_on_front_id() ) {
|
||||
$front_page = $this->get_url(
|
||||
get_post( $this->get_page_on_front_id() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $front_page ) ) {
|
||||
$front_page = array(
|
||||
'loc' => $this->get_home_url(),
|
||||
);
|
||||
}
|
||||
|
||||
// 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[] = array(
|
||||
'loc' => $archive_url,
|
||||
'mod' => WPSEO_Sitemaps::get_last_modified_gmt( $post_type ),
|
||||
|
||||
// Deprecated, kept for backwards data compat. R.
|
||||
'chf' => 'daily',
|
||||
'pri' => 1,
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
|
||||
if ( WPSEO_Options::get( 'noindex-ptarchive-' . $post_type, false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Post archive should be excluded if it isn't front page or posts page.
|
||||
if ( $post_type === 'post' && get_option( 'show_on_front' ) !== 'posts' && ! $this->get_page_for_posts_id() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the page which is dedicated to this post type archive.
|
||||
*
|
||||
* @param string $post_id The post_id of the page.
|
||||
* @param string $post_type The post type this archive is for.
|
||||
*/
|
||||
$post_id = (int) apply_filters(
|
||||
'wpseo_sitemap_page_for_post_type_archive',
|
||||
( 'post' === $post_type ) ? $this->get_page_for_posts_id() : ( -1 ),
|
||||
$post_type
|
||||
);
|
||||
|
||||
if ( $post_id > 0 && WPSEO_Meta::get_value( 'meta-robots-noindex', $post_id ) === '1' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_post_type_archive_link( $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
|
||||
if ( ! isset( $filters[ $post_type ] ) ) {
|
||||
// Make sure you're wpdb->preparing everything you throw into this!!
|
||||
$filters[ $post_type ] = array(
|
||||
/**
|
||||
* 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_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 = array();
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
$post->post_type = $post_type;
|
||||
$post->post_status = 'publish';
|
||||
$post->filter = 'sample';
|
||||
$post->ID = (int) $post->ID;
|
||||
$post->post_parent = (int) $post->post_parent;
|
||||
$post->post_author = (int) $post->post_author;
|
||||
$post_ids[] = $post->ID;
|
||||
}
|
||||
|
||||
update_meta_cache( 'post', $post_ids );
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $post_type Post type slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_sql_where_clause( $post_type ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$join = '';
|
||||
$status = "{$wpdb->posts}.post_status = 'publish'";
|
||||
|
||||
// Based on WP_Query->get_posts(). R.
|
||||
if ( 'attachment' === $post_type ) {
|
||||
$join = " LEFT JOIN {$wpdb->posts} AS p2 ON ({$wpdb->posts}.post_parent = p2.ID) ";
|
||||
$status = "p2.post_status = 'publish' AND p2.post_password = ''";
|
||||
}
|
||||
|
||||
$where_clause = "
|
||||
{$join}
|
||||
WHERE {$status}
|
||||
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 = array();
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
/*
|
||||
* Do not include external URLs.
|
||||
*
|
||||
* {@link https://wordpress.org/plugins/page-links-to/} can rewrite permalinks to external URLs.
|
||||
*/
|
||||
if ( $this->get_classifier()->classify( $url['loc'] ) === WPSEO_Link::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.
|
||||
$url['images'] = $this->get_image_parser()->get_images( $post );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/* ********************* DEPRECATED METHODS ********************* */
|
||||
|
||||
/**
|
||||
* Get all the options.
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected function get_options() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 7.0', 'WPSEO_Options::get' );
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sitemap Cache Data object, manages sitemap data stored in cache.
|
||||
*/
|
||||
class WPSEO_Sitemap_Cache_Data implements WPSEO_Sitemap_Cache_Data_Interface, Serializable {
|
||||
|
||||
/**
|
||||
* 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 $valid Is the sitemap valid or not.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_status( $valid ) {
|
||||
|
||||
if ( self::OK === $valid ) {
|
||||
$this->status = self::OK;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( self::ERROR === $valid ) {
|
||||
$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 self::OK === $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @link http://php.net/manual/en/serializable.serialize.php
|
||||
*
|
||||
* @since 5.1.0
|
||||
*
|
||||
* @return string The string representation of the object or null.
|
||||
*/
|
||||
public function serialize() {
|
||||
|
||||
$data = array(
|
||||
'status' => $this->status,
|
||||
'xml' => $this->sitemap,
|
||||
);
|
||||
|
||||
return serialize( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the object.
|
||||
*
|
||||
* @link http://php.net/manual/en/serializable.unserialize.php
|
||||
*
|
||||
* @since 5.1.0
|
||||
*
|
||||
* @param string $serialized The string representation of the object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unserialize( $serialized ) {
|
||||
|
||||
$data = unserialize( $serialized );
|
||||
$this->set_sitemap( $data['xml'] );
|
||||
$this->set_status( $data['status'] );
|
||||
}
|
||||
}
|
@ -0,0 +1,520 @@
|
||||
<?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 = array();
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
|
||||
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 ) );
|
||||
$alt = WPSEO_Image_Utils::get_alt_tag( $thumbnail_id );
|
||||
$title = get_post_field( 'post_title', $thumbnail_id );
|
||||
$images[] = $this->get_image_item( $post, $src, $title, $alt );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'], $image['title'], $image['alt'] );
|
||||
}
|
||||
|
||||
foreach ( $this->parse_galleries( $post->post_content, $post->ID ) as $attachment ) {
|
||||
|
||||
$src = $this->get_absolute_url( $this->image_url( $attachment->ID ) );
|
||||
$alt = WPSEO_Image_Utils::get_alt_tag( $attachment->ID );
|
||||
|
||||
$images[] = $this->get_image_item( $post, $src, $attachment->post_title, $alt );
|
||||
}
|
||||
|
||||
if ( 'attachment' === $post->post_type && wp_attachment_is_image( $post ) ) {
|
||||
|
||||
$src = $this->get_absolute_url( $this->image_url( $post->ID ) );
|
||||
$alt = WPSEO_Image_Utils::get_alt_tag( $post->ID );
|
||||
|
||||
$images[] = $this->get_image_item( $post, $src, $post->post_title, $alt );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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[] = array(
|
||||
'src' => $this->get_absolute_url( $this->image_url( $attachment->ID ) ),
|
||||
'title' => $attachment->post_title,
|
||||
'alt' => WPSEO_Image_Utils::get_alt_tag( $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 = array();
|
||||
|
||||
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();
|
||||
|
||||
/** @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 )
|
||||
&& false === strpos( $class, 'size-full' )
|
||||
&& preg_match( '|wp-image-(?P<id>\d+)|', $class, $matches )
|
||||
&& get_post_status( $matches['id'] )
|
||||
) {
|
||||
$src = $this->image_url( $matches['id'] );
|
||||
}
|
||||
|
||||
$src = $this->get_absolute_url( $src );
|
||||
|
||||
if ( strpos( $src, $this->host ) === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $src !== esc_url( $src ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$images[] = array(
|
||||
'src' => $src,
|
||||
'title' => $img->getAttribute( 'title' ),
|
||||
'alt' => $img->getAttribute( 'alt' ),
|
||||
);
|
||||
}
|
||||
|
||||
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 = array();
|
||||
$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 );
|
||||
}
|
||||
|
||||
if ( PHP_VERSION_ID >= 50209 ) {
|
||||
// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.array_unique_sort_flagsFound -- Wrapped in version check.
|
||||
return array_unique( $attachments, SORT_REGULAR );
|
||||
}
|
||||
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
|
||||
if ( ! has_shortcode( $content, 'gallery' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$galleries = array();
|
||||
|
||||
if ( ! preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER ) ) {
|
||||
return $galleries;
|
||||
}
|
||||
|
||||
foreach ( $matches as $shortcode ) {
|
||||
if ( 'gallery' === $shortcode[2] ) {
|
||||
|
||||
$attributes = shortcode_parse_atts( $shortcode[3] );
|
||||
|
||||
if ( '' === $attributes ) { // Valid shortcode without any attributes. R.
|
||||
$attributes = array();
|
||||
}
|
||||
|
||||
$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.
|
||||
* @param string $title Optional image title.
|
||||
* @param string $alt Optional image alt text.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_image_item( $post, $src, $title = '', $alt = '' ) {
|
||||
|
||||
$image = array();
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
if ( ! empty( $title ) ) {
|
||||
$image['title'] = $title;
|
||||
}
|
||||
|
||||
if ( ! empty( $alt ) ) {
|
||||
$image['alt'] = $alt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter image data to be included in XML sitemap for the post.
|
||||
*
|
||||
* @param array $image {
|
||||
* Array of image data.
|
||||
*
|
||||
* @type string $src Image URL.
|
||||
* @type string $title Image title attribute (optional).
|
||||
* @type string $alt Image alt attribute (optional).
|
||||
* }
|
||||
*
|
||||
* @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 ( false !== $uploads['error'] ) {
|
||||
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 ( 0 === strpos( $file, $uploads['basedir'] ) ) {
|
||||
$src = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
|
||||
}
|
||||
elseif ( false !== strpos( $file, 'wp-content/uploads' ) ) {
|
||||
$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 ( WPSEO_Utils::is_url_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 array();
|
||||
}
|
||||
|
||||
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 = array(
|
||||
'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 $include Array with IDs to include.
|
||||
*
|
||||
* @return array The found attachments.
|
||||
*/
|
||||
protected function get_gallery_attachments_for_included( $include ) {
|
||||
$ids_to_include = wp_parse_id_list( $include );
|
||||
$attachments = $this->get_attachments(
|
||||
array(
|
||||
'posts_per_page' => count( $ids_to_include ),
|
||||
'post__in' => $ids_to_include,
|
||||
)
|
||||
);
|
||||
|
||||
$gallery_attachments = array();
|
||||
foreach ( $attachments as $key => $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 = array(
|
||||
'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,123 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Sitemap_Timezone.
|
||||
*/
|
||||
class WPSEO_Sitemap_Timezone {
|
||||
|
||||
/**
|
||||
* Holds the timezone string value to reuse for performance.
|
||||
*
|
||||
* @var string $timezone_string
|
||||
*/
|
||||
private $timezone_string = '';
|
||||
|
||||
/**
|
||||
* Format arbitrary UTC datetime string to desired form in site's time zone.
|
||||
*
|
||||
* @param string $datetime_string The input datetime string in UTC time zone.
|
||||
* @param string $format Date format to use.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format_date( $datetime_string, $format = 'c' ) {
|
||||
|
||||
$date_time = $this->get_datetime_with_timezone( $datetime_string );
|
||||
|
||||
if ( is_null( $date_time ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $date_time->format( $format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the datetime object, in site's time zone, if the datetime string was valid
|
||||
*
|
||||
* @param string $datetime_string The datetime string in UTC time zone, that needs
|
||||
* to be converted to a DateTime object.
|
||||
*
|
||||
* @return DateTime|null DateTime object in site's time zone.
|
||||
*/
|
||||
public function get_datetime_with_timezone( $datetime_string ) {
|
||||
|
||||
static $utc_timezone, $local_timezone;
|
||||
|
||||
if ( ! isset( $utc_timezone ) ) {
|
||||
$utc_timezone = new DateTimeZone( 'UTC' );
|
||||
$local_timezone = new DateTimeZone( $this->get_timezone_string() );
|
||||
}
|
||||
|
||||
if ( ! empty( $datetime_string ) && WPSEO_Utils::is_valid_datetime( $datetime_string ) ) {
|
||||
$datetime = new DateTime( $datetime_string, $utc_timezone );
|
||||
$datetime->setTimezone( $local_timezone );
|
||||
|
||||
return $datetime;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timezone string for a site, even if it's set to a UTC offset.
|
||||
*
|
||||
* Adapted from {@link http://www.php.net/manual/en/function.timezone-name-from-abbr.php#89155}.
|
||||
*
|
||||
* @return string Valid PHP timezone string.
|
||||
*/
|
||||
private function determine_timezone_string() {
|
||||
|
||||
// If site timezone string exists, return it.
|
||||
$timezone = get_option( 'timezone_string' );
|
||||
if ( ! empty( $timezone ) ) {
|
||||
return $timezone;
|
||||
}
|
||||
|
||||
// Get UTC offset, if it isn't set then return UTC.
|
||||
$utc_offset = (int) get_option( 'gmt_offset', 0 );
|
||||
if ( 0 === $utc_offset ) {
|
||||
return 'UTC';
|
||||
}
|
||||
|
||||
// Adjust UTC offset from hours to seconds.
|
||||
$utc_offset *= HOUR_IN_SECONDS;
|
||||
|
||||
// Attempt to guess the timezone string from the UTC offset.
|
||||
$timezone = timezone_name_from_abbr( '', $utc_offset );
|
||||
|
||||
if ( false !== $timezone ) {
|
||||
return $timezone;
|
||||
}
|
||||
|
||||
// Last try, guess timezone string manually.
|
||||
$timezone_list = timezone_abbreviations_list();
|
||||
foreach ( $timezone_list as $abbr ) {
|
||||
foreach ( $abbr as $city ) {
|
||||
if ( $city['offset'] === $utc_offset ) {
|
||||
return $city['timezone_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to UTC.
|
||||
return 'UTC';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct timezone string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_timezone_string() {
|
||||
if ( '' === $this->timezone_string ) {
|
||||
$this->timezone_string = $this->determine_timezone_string();
|
||||
}
|
||||
|
||||
return $this->timezone_string;
|
||||
}
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
<?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 = array();
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'transition_post_status', array( $this, 'status_transition' ), 10, 3 );
|
||||
add_action( 'admin_footer', array( $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 ( 'nav_menu_item' === $post_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the post type is excluded in options, we can stop.
|
||||
if ( WPSEO_Options::get( 'noindex-' . $post_type, false ) ) {
|
||||
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;
|
||||
}
|
||||
|
||||
if ( defined( 'YOAST_SEO_PING_IMMEDIATELY' ) && YOAST_SEO_PING_IMMEDIATELY ) {
|
||||
WPSEO_Sitemaps::ping_search_engines();
|
||||
}
|
||||
elseif ( ! wp_next_scheduled( 'wpseo_ping_search_engines' ) ) {
|
||||
wp_schedule_single_event( ( time() + 300 ), 'wpseo_ping_search_engines' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ( 'nav_menu_item' === $post_type ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( WPSEO_Options::get( 'noindex-' . $post_type, false ) === false ) {
|
||||
$ping_search_engines = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing to do.
|
||||
if ( false === $ping_search_engines ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WP_CACHE ) {
|
||||
do_action( 'wpseo_hit_sitemap_index' );
|
||||
}
|
||||
|
||||
WPSEO_Sitemaps::ping_search_engines();
|
||||
}
|
||||
|
||||
/* ********************* DEPRECATED METHODS ********************* */
|
||||
|
||||
/**
|
||||
* Find sitemaps residing on disk as they will block our rewrite.
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function delete_sitemaps() {
|
||||
_deprecated_function( 'WPSEO_Sitemaps_Admin::delete_sitemaps', '7.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Find sitemaps residing on disk as they will block our rewrite.
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function detect_blocking_filesystem_sitemaps() {
|
||||
_deprecated_function( 'WPSEO_Sitemaps_Admin::delete_sitemaps', '7.0' );
|
||||
}
|
||||
} /* End of class */
|
@ -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 null|string $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 null|string $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 null|string $type The type of sitemap to clear cache for.
|
||||
* @param null|string $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 = array();
|
||||
$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 null|string 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.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
* This is base64 (numeric + alpha + alpha upper case) without the 0.
|
||||
*
|
||||
* @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,347 @@
|
||||
<?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 = array();
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
|
||||
/**
|
||||
* Hook methods for invalidation on necessary events.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'init', array( $this, 'init' ) );
|
||||
|
||||
add_action( 'deleted_term_relationships', array( __CLASS__, 'invalidate' ) );
|
||||
|
||||
add_action( 'update_option', array( __CLASS__, 'clear_on_option_update' ) );
|
||||
|
||||
add_action( 'edited_terms', array( __CLASS__, 'invalidate_helper' ), 10, 2 );
|
||||
add_action( 'clean_term_cache', array( __CLASS__, 'invalidate_helper' ), 10, 2 );
|
||||
add_action( 'clean_object_term_cache', array( __CLASS__, 'invalidate_helper' ), 10, 2 );
|
||||
|
||||
add_action( 'user_register', array( __CLASS__, 'invalidate_author' ) );
|
||||
add_action( 'delete_user', array( __CLASS__, 'invalidate_author' ) );
|
||||
|
||||
add_action( 'shutdown', array( __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 boolean
|
||||
*/
|
||||
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|boolean
|
||||
*/
|
||||
public function get_sitemap( $type, $page ) {
|
||||
|
||||
$transient_key = WPSEO_Sitemaps_Cache_Validator::get_storage_key( $type, $page );
|
||||
if ( false === $transient_key ) {
|
||||
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 null|WPSEO_Sitemap_Cache_Data 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 (is_serialized doesn't recognize classes).
|
||||
if ( is_string( $sitemap ) && 0 === strpos( $sitemap, 'C:24:"WPSEO_Sitemap_Cache_Data"' ) ) {
|
||||
|
||||
$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 ( false === $transient_key ) {
|
||||
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( array( $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 ( 'user_register' === current_action() ) {
|
||||
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 = array() ) {
|
||||
|
||||
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 ) ) {
|
||||
array_unshift( $types, WPSEO_Sitemaps::SITEMAP_INDEX_TYPE );
|
||||
}
|
||||
|
||||
foreach ( $types as $type ) {
|
||||
if ( ! in_array( $type, self::$clear_types ) ) {
|
||||
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 = array();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( self::$clear_types as $type ) {
|
||||
WPSEO_Sitemaps_Cache_Validator::invalidate_storage( $type );
|
||||
}
|
||||
|
||||
self::$clear_types = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,372 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Timezone.
|
||||
*
|
||||
* @var WPSEO_Sitemap_Timezone
|
||||
*/
|
||||
protected $timezone;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
$this->timezone = new WPSEO_Sitemap_Timezone();
|
||||
|
||||
if (
|
||||
'UTF-8' !== $this->charset
|
||||
&& 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @param boolean $transient Transient cache flag.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_output( $sitemap, $transient ) {
|
||||
|
||||
$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 -->";
|
||||
|
||||
$debug = WP_DEBUG || ( defined( 'WPSEO_DEBUG' ) && true === WPSEO_DEBUG );
|
||||
|
||||
if ( ! WP_DEBUG_DISPLAY || ! $debug ) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
$memory_used = number_format( ( memory_get_peak_usage() / 1048576 ), 2 );
|
||||
$queries_run = ( $transient ) ? 'Served from transient cache' : 'Queries executed ' . absint( $GLOBALS['wpdb']->num_queries );
|
||||
|
||||
$output .= "\n<!-- {$memory_used}MB | {$queries_run} -->";
|
||||
|
||||
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
|
||||
|
||||
$queries = print_r( $GLOBALS['wpdb']->queries, true );
|
||||
$output .= "\n<!-- {$queries} -->";
|
||||
}
|
||||
|
||||
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 = $this->timezone->format_date( $url['lastmod'] );
|
||||
}
|
||||
|
||||
$url['loc'] = htmlspecialchars( $url['loc'] );
|
||||
|
||||
$output = "\t<sitemap>\n";
|
||||
$output .= "\t\t<loc>" . $url['loc'] . "</loc>\n";
|
||||
$output .= empty( $date ) ? '' : "\t\t<lastmod>" . htmlspecialchars( $date ) . "</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 = $this->timezone->format_date( $url['mod'] );
|
||||
}
|
||||
|
||||
$url['loc'] = htmlspecialchars( $url['loc'] );
|
||||
|
||||
$output = "\t<url>\n";
|
||||
$output .= "\t\t<loc>" . $this->encode_url_rfc3986( $url['loc'] ) . "</loc>\n";
|
||||
$output .= empty( $date ) ? '' : "\t\t<lastmod>" . htmlspecialchars( $date ) . "</lastmod>\n";
|
||||
|
||||
if ( empty( $url['images'] ) ) {
|
||||
$url['images'] = array();
|
||||
}
|
||||
|
||||
foreach ( $url['images'] as $img ) {
|
||||
|
||||
if ( empty( $img['src'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$output .= "\t\t<image:image>\n";
|
||||
$output .= "\t\t\t<image:loc>" . esc_html( $this->encode_url_rfc3986( $img['src'] ) ) . "</image:loc>\n";
|
||||
|
||||
if ( ! empty( $img['title'] ) ) {
|
||||
|
||||
$title = $img['title'];
|
||||
|
||||
if ( $this->needs_conversion ) {
|
||||
$title = mb_convert_encoding( $title, $this->output_charset, $this->charset );
|
||||
}
|
||||
|
||||
$title = _wp_specialchars( html_entity_decode( $title, ENT_QUOTES, $this->output_charset ) );
|
||||
$output .= "\t\t\t<image:title><![CDATA[{$title}]]></image:title>\n";
|
||||
}
|
||||
|
||||
if ( ! empty( $img['alt'] ) ) {
|
||||
|
||||
$alt = $img['alt'];
|
||||
|
||||
if ( $this->needs_conversion ) {
|
||||
$alt = mb_convert_encoding( $alt, $this->output_charset, $this->charset );
|
||||
}
|
||||
|
||||
$alt = _wp_specialchars( html_entity_decode( $alt, ENT_QUOTES, $this->output_charset ) );
|
||||
$output .= "\t\t\t<image:caption><![CDATA[{$alt}]]></image:caption>\n";
|
||||
}
|
||||
|
||||
$output .= "\t\t</image:image>\n";
|
||||
}
|
||||
unset( $img, $title, $alt );
|
||||
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
$encoded_path = str_replace( '%7E', '~', $encoded_path ); // PHP < 5.3.
|
||||
|
||||
$url = str_replace( $path, $encoded_path, $url );
|
||||
}
|
||||
|
||||
$query = wp_parse_url( $url, PHP_URL_QUERY );
|
||||
|
||||
if ( ! empty( $query ) ) {
|
||||
|
||||
parse_str( $query, $parsed_query );
|
||||
|
||||
if ( defined( 'PHP_QUERY_RFC3986' ) ) { // PHP 5.4+.
|
||||
$parsed_query = http_build_query( $parsed_query, null, '&', PHP_QUERY_RFC3986 );
|
||||
}
|
||||
else {
|
||||
$parsed_query = http_build_query( $parsed_query, null, '&' );
|
||||
$parsed_query = str_replace( '+', '%20', $parsed_query );
|
||||
$parsed_query = str_replace( '%7E', '~', $parsed_query );
|
||||
}
|
||||
|
||||
$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,128 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\XML_Sitemaps
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rewrite setup and handling for sitemaps functionality.
|
||||
*/
|
||||
class WPSEO_Sitemaps_Router {
|
||||
|
||||
/**
|
||||
* Sets up init logic.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'init', array( $this, 'init' ), 1 );
|
||||
add_filter( 'redirect_canonical', array( $this, 'redirect_canonical' ) );
|
||||
add_action( 'template_redirect', array( $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( '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?xsl=$matches[1]', 'top' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop trailing slashes on sitemap.xml URLs.
|
||||
*
|
||||
* @param string $redirect The redirect URL currently determined.
|
||||
*
|
||||
* @return bool|string $redirect
|
||||
*/
|
||||
public function redirect_canonical( $redirect ) {
|
||||
|
||||
if ( get_query_var( 'sitemap' ) || get_query_var( 'xsl' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects sitemap.xml to sitemap_index.xml.
|
||||
*/
|
||||
public function template_redirect() {
|
||||
if ( ! $this->needs_sitemap_index_redirect() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
header( 'X-Redirect-By: Yoast SEO' );
|
||||
wp_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 = array( $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 ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,585 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @var WPSEO_Sitemap_Timezone
|
||||
*/
|
||||
private $timezone;
|
||||
|
||||
/**
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemaps_Router
|
||||
*/
|
||||
public $router;
|
||||
|
||||
/**
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemaps_Renderer
|
||||
*/
|
||||
public $renderer;
|
||||
|
||||
/**
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemaps_Cache
|
||||
*/
|
||||
public $cache;
|
||||
|
||||
/**
|
||||
* @since 3.2
|
||||
*
|
||||
* @var WPSEO_Sitemap_Provider[]
|
||||
*/
|
||||
public $providers;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
add_action( 'after_setup_theme', array( $this, 'init_sitemaps_providers' ) );
|
||||
add_action( 'after_setup_theme', array( $this, 'reduce_query_load' ), 99 );
|
||||
add_action( 'pre_get_posts', array( $this, 'redirect' ), 1 );
|
||||
add_action( 'wpseo_hit_sitemap_index', array( $this, 'hit_sitemap_index' ) );
|
||||
add_action( 'wpseo_ping_search_engines', array( __CLASS__, 'ping_search_engines' ) );
|
||||
|
||||
$this->timezone = new WPSEO_Sitemap_Timezone();
|
||||
$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 = array(
|
||||
new WPSEO_Post_Type_Sitemap_Provider(),
|
||||
new WPSEO_Taxonomy_Sitemap_Provider(),
|
||||
new WPSEO_Author_Sitemap_Provider(),
|
||||
);
|
||||
|
||||
$external_providers = apply_filters( 'wpseo_sitemaps_providers', array() );
|
||||
|
||||
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 ( false !== stripos( $request_uri, 'sitemap' ) && in_array( $extension, array( '.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 $function Function to build your sitemap.
|
||||
* @param string $rewrite Optional. Regular expression to match your sitemap with.
|
||||
*/
|
||||
public function register_sitemap( $name, $function, $rewrite = '' ) {
|
||||
add_action( 'wpseo_do_sitemap_' . $name, $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 $function Function to build your XSL file.
|
||||
* @param string $rewrite Optional. Regular expression to match your sitemap with.
|
||||
*/
|
||||
public function register_xsl( $name, $function, $rewrite = '' ) {
|
||||
add_action( 'wpseo_xsl_' . $name, $function );
|
||||
if ( ! empty( $rewrite ) ) {
|
||||
add_rewrite_rule( $rewrite, 'index.php?xsl=' . $name, 'top' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sitemap current page to allow creating partial sitemaps with WP-CLI
|
||||
* in a one-off process.
|
||||
*
|
||||
* @param integer $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 $bool Is this a bad request. True or false.
|
||||
*/
|
||||
public function set_bad_sitemap( $bool ) {
|
||||
$this->bad_sitemap = (bool) $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
$xsl = get_query_var( 'xsl' );
|
||||
|
||||
if ( ! empty( $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( $xsl );
|
||||
$this->sitemap_close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$type = get_query_var( 'sitemap' );
|
||||
|
||||
if ( empty( $type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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 ( true !== $this->cache->is_enabled() ) {
|
||||
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;
|
||||
}
|
||||
|
||||
$links = $provider->get_sitemap_links( $type, $entries_per_page, $this->current_page );
|
||||
|
||||
if ( empty( $links ) ) {
|
||||
$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 = array();
|
||||
$entries_per_page = $this->get_entries_per_page();
|
||||
|
||||
foreach ( $this->providers as $provider ) {
|
||||
$links = array_merge( $links, $provider->get_index_links( $entries_per_page ) );
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param string $type Type to output.
|
||||
*
|
||||
* @since 1.4.13
|
||||
*/
|
||||
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: maxage=' . $expires );
|
||||
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', ( time() + $expires ) ) . ' GMT' );
|
||||
|
||||
readfile( WPSEO_PATH . 'css/main-sitemap.xsl' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Spit out the generated sitemap and relevant headers and encoding information.
|
||||
*/
|
||||
public function output() {
|
||||
|
||||
if ( ! headers_sent() ) {
|
||||
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; charset=' . esc_attr( $this->renderer->get_output_charset() ) );
|
||||
}
|
||||
|
||||
echo $this->renderer->get_output( $this->sitemap, $this->transient );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = array( $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 = array();
|
||||
|
||||
// Consider using WPSEO_Post_Type::get_accessible_post_types() to filter out any `no-index` post-types.
|
||||
$post_type_names = get_post_types( array( 'public' => true ) );
|
||||
|
||||
if ( ! empty( $post_type_names ) ) {
|
||||
$sql = "
|
||||
SELECT post_type, MAX(post_modified_gmt) AS date
|
||||
FROM $wpdb->posts
|
||||
WHERE post_status IN ('publish','inherit')
|
||||
AND post_type IN ('" . implode( "','", $post_type_names ) . "')
|
||||
GROUP BY post_type
|
||||
ORDER BY post_modified_gmt DESC
|
||||
";
|
||||
|
||||
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 $this->timezone->format_date( self::get_last_modified_gmt( $post_types ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify search engines of the updated sitemap.
|
||||
*
|
||||
* @param string|null $url Optional URL to make the ping for.
|
||||
*/
|
||||
public static function ping_search_engines( $url = null ) {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
if ( '0' === get_option( 'blog_public' ) ) { // Don't ping if blog is not public.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $url ) ) {
|
||||
$url = urlencode( WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ) );
|
||||
}
|
||||
|
||||
// Ping Google and Bing.
|
||||
wp_remote_get( 'http://www.google.com/webmasters/tools/ping?sitemap=' . $url, array( 'blocking' => false ) );
|
||||
wp_remote_get( 'http://www.bing.com/ping?sitemap=' . $url, array( 'blocking' => false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,277 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Check if provider supports given item type.
|
||||
*
|
||||
* @param string $type Type string to check for.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function handles_type( $type ) {
|
||||
|
||||
$taxonomy = get_taxonomy( $type );
|
||||
|
||||
if ( $taxonomy === false || ! $this->is_valid_taxonomy( $taxonomy->name ) || ! $taxonomy->public ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $max_entries Entries per sitemap.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_index_links( $max_entries ) {
|
||||
|
||||
$taxonomies = get_taxonomies( array( 'public' => true ), 'objects' );
|
||||
|
||||
if ( empty( $taxonomies ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$taxonomy_names = array_filter( array_keys( $taxonomies ), array( $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 boolean $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 = array();
|
||||
|
||||
$term_args = array(
|
||||
'hide_empty' => $hide_empty,
|
||||
'fields' => 'ids',
|
||||
);
|
||||
|
||||
foreach ( $taxonomy_names as $taxonomy_name ) {
|
||||
$taxonomy_terms = get_terms( $taxonomy_name, $term_args );
|
||||
|
||||
if ( count( $taxonomy_terms ) > 0 ) {
|
||||
$all_taxonomies[ $taxonomy_name ] = $taxonomy_terms;
|
||||
}
|
||||
}
|
||||
|
||||
$index = array();
|
||||
|
||||
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 = ( $max_pages > 1 ) ? ( $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 = array(
|
||||
'post_type' => $tax->object_type,
|
||||
'tax_query' => array(
|
||||
array(
|
||||
'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[] = array(
|
||||
'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
|
||||
*/
|
||||
public function get_sitemap_links( $type, $max_entries, $current_page ) {
|
||||
global $wpdb;
|
||||
|
||||
$links = array();
|
||||
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, array( $taxonomy->name ) );
|
||||
$terms = get_terms( $taxonomy->name, array( 'hide_empty' => $hide_empty ) );
|
||||
$terms = array_splice( $terms, $offset, $steps );
|
||||
|
||||
if ( empty( $terms ) ) {
|
||||
$terms = array();
|
||||
}
|
||||
|
||||
// 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 ('publish','inherit')
|
||||
AND p.post_password = ''
|
||||
";
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
|
||||
$url = array();
|
||||
|
||||
$tax_noindex = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'noindex' );
|
||||
|
||||
if ( $tax_noindex === 'noindex' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url['loc'] = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'canonical' );
|
||||
|
||||
if ( ! is_string( $url['loc'] ) || $url['loc'] === '' ) {
|
||||
$url['loc'] = get_term_link( $term, $term->taxonomy );
|
||||
}
|
||||
|
||||
$url['mod'] = $wpdb->get_var( $wpdb->prepare( $sql, $term->taxonomy, $term->term_id ) );
|
||||
$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, array( 'link_category', 'nav_menu' ), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'post_format' === $taxonomy_name && WPSEO_Options::get( 'disable-post_format', false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to exclude the taxonomy from the XML sitemap.
|
||||
*
|
||||
* @param boolean $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;
|
||||
}
|
||||
|
||||
/* ********************* DEPRECATED METHODS ********************* */
|
||||
|
||||
/**
|
||||
* Get all the options.
|
||||
*
|
||||
* @deprecated 7.0
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected function get_options() {
|
||||
_deprecated_function( __METHOD__, 'WPSEO 7.0', 'WPSEO_Options::get' );
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<?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 );
|
||||
|
||||
/**
|
||||
* @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 boolean
|
||||
*/
|
||||
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