: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @return string|array|false
public static function get_last_modified_gmt( $post_types, $return_all = false ) {
static $post_type_dates = null;
if ( ! is_array( $post_types ) ) {
$post_types = [ $post_types ];
foreach ( $post_types as $post_type ) {
if ( ! isset( $post_type_dates[ $post_type ] ) ) { // If we hadn't seen post type before. R.
if ( is_null( $post_type_dates ) ) {
$post_type_names = WPSEO_Post_Type::get_accessible_post_types();
if ( ! empty( $post_type_names ) ) {
$post_statuses = array_map( 'esc_sql', self::get_post_statuses() );
$replacements = array_merge(
array_keys( $post_type_names ),
//phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need to use a direct query here.
//phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
$dates = $wpdb->get_results(
//phpcs:disable WordPress.DB.PreparedSQLPlaceholders -- %i placeholder is still not recognized.
WHERE %i IN (' . implode( ', ', array_fill( 0, count( $post_statuses ), '%s' ) ) . ')
AND %i IN (' . implode( ', ', array_fill( 0, count( $post_type_names ), '%s' ) ) . ')
foreach ( $dates 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 ) {
* 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.
public function get_last_modified( $post_types ) {
return YoastSEO()->helpers->date->format( self::get_last_modified_gmt( $post_types ) );
* 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 );
* 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.
* @return array List of post statuses.
public static function get_post_statuses( $type = self::SITEMAP_INDEX_TYPE ) {
* Filter post status list for sitemap query for the post type.
* @param array $post_statuses Post status list, defaults to array( 'publish' ).
* @param string $type Post type or SITEMAP_INDEX_TYPE.
$post_statuses = apply_filters( 'wpseo_sitemap_post_statuses', [ 'publish' ], $type );
if ( ! is_array( $post_statuses ) || empty( $post_statuses ) ) {
$post_statuses = [ 'publish' ];
if ( ( $type === self::SITEMAP_INDEX_TYPE || $type === 'attachment' )
&& ! in_array( 'inherit', $post_statuses, true )
$post_statuses[] = 'inherit';
* Sends all the required HTTP Headers.
private function send_headers() {
$this->http_protocol . ' 200 OK' => 200,
// Prevent the search engines from indexing the XML Sitemap.
'X-Robots-Tag: noindex, follow' => '',
'Content-Type: text/xml; charset=' . esc_attr( $this->renderer->get_output_charset() ) => '',
* Filter the HTTP headers we send before an XML sitemap.
* @param array $headers The HTTP headers we're going to send out.
$headers = apply_filters( 'wpseo_sitemap_http_headers', $headers );
foreach ( $headers as $header => $status ) {
if ( is_numeric( $status ) ) {
header( $header, true, $status );