e of sitemap to build. * * @param string $type Sitemap type, determined by the request. */ $type = apply_filters( 'wpseo_build_sitemap_post_type', $type ); if ( $type === '1' ) { $this->build_root_map(); return; } $entries_per_page = $this->get_entries_per_page(); foreach ( $this->providers as $provider ) { if ( ! $provider->handles_type( $type ) ) { continue; } try { $links = $provider->get_sitemap_links( $type, $entries_per_page, $this->current_page ); } catch ( OutOfBoundsException $exception ) { $this->bad_sitemap = true; return; } $this->sitemap = $this->renderer->get_sitemap( $links, $type, $this->current_page ); return; } if ( has_action( 'wpseo_do_sitemap_' . $type ) ) { /** * Fires custom handler, if hooked to generate sitemap for the type. */ do_action( 'wpseo_do_sitemap_' . $type ); return; } $this->bad_sitemap = true; } /** * Build the root sitemap (example.com/sitemap_index.xml) which lists sub-sitemaps for other content types. */ public function build_root_map() { $links = 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. */ public function output() { $this->send_headers(); 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 ) ) { $post_statuses = array_map( 'esc_sql', self::get_post_statuses() ); $sql = " SELECT post_type, MAX(post_modified_gmt) AS date FROM $wpdb->posts WHERE post_status IN ('" . implode( "','", $post_statuses ) . "') AND post_type IN ('" . implode( "','", $post_type_names ) . "') GROUP BY post_type ORDER BY 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( 'https://www.google.com/ping?sitemap=' . $url, array( 'blocking' => false ) ); wp_remote_get( 'https://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; } /** * Get post statuses for post_type or the root sitemap. * * @param string $type Provide a type for a post_type sitemap, SITEMAP_INDEX_TYPE for the root sitemap. * * @since 10.2 * * @return array List of post statuses. */ public static function get_post_statuses( $type = self::SITEMAP_INDEX_TYPE ) { /** * Filter post status list for sitemap query for the post type. * * @param array $post_statuses Post status list, defaults to array( 'publish' ). * @param string $type Post type or SITEMAP_INDEX_TYPE. */ $post_statuses = apply_filters( 'wpseo_sitemap_post_statuses', array( 'publish' ), $type ); if ( ! is_array( $post_statuses ) || empty( $post_statuses ) ) { $post_statuses = array( 'publish' ); } if ( ( $type === self::SITEMAP_INDEX_TYPE || $type === 'attachment' ) && ! in_array( 'inherit', $post_statuses, true ) ) { $post_statuses[] = 'inherit'; } return $post_statuses; } /** * Sends all the required HTTP Headers. */ private function send_headers() { if ( headers_sent() ) { return; } $headers = array( $this->http_protocol . ' 200 OK' => 200, // Prevent the search engines from indexing the XML Sitemap. 'X-Robots-Tag: noindex, follow' => '', 'Content-Type: text/xml; charset=' . esc_attr( $this->renderer->get_output_charset() ) => '', ); /** * Filter the HTTP headers we send before an XML sitemap. * * @param array $headers The HTTP headers we're going to send out. */ $headers = apply_filters( 'wpseo_sitemap_http_headers', $headers ); foreach ( $headers as $header => $status ) { if ( is_numeric( $status ) ) { header( $header, true, $status ); continue; } header( $header, true ); } } }