wp-includes/class-wp.php:435Sends additional HTTP headers for caching, content type, etc.
2 hooks fire while WP::send_headers() runs, in this order:
Filters the HTTP headers before they're sent to the browser.
Fires once the requested HTTP headers for caching, content type, etc. have been sent.
public function send_headers() { global $wp_query; $headers = array(); $status = null; $exit_required = false; $date_format = 'D, d M Y H:i:s'; if ( is_user_logged_in() ) { $headers = array_merge( $headers, wp_get_nocache_headers() ); } elseif ( ! empty( $_GET['unapproved'] ) && ! empty( $_GET['moderation-hash'] ) ) { // Unmoderated comments are only visible for 10 minutes via the moderation hash. $expires = 10 * MINUTE_IN_SECONDS; $headers['Expires'] = gmdate( $date_format, time() + $expires ); $headers['Cache-Control'] = sprintf( 'max-age=%d, must-revalidate', $expires ); } if ( ! empty( $this->query_vars['error'] ) ) { $status = (int) $this->query_vars['error']; if ( 404 === $status ) { if ( ! is_user_logged_in() ) { $headers = array_merge( $headers, wp_get_nocache_headers() ); } $headers['Content-Type'] = get_option( 'html_type' ) . '; charset=' . get_option( 'blog_charset' ); } elseif ( in_array( $status, array( 403, 500, 502, 503 ), true ) ) { $exit_required = true; } } elseif ( empty( $this->query_vars['feed'] ) ) { $headers['Content-Type'] = get_option( 'html_type' ) . '; charset=' . get_option( 'blog_charset' ); } else { // Set the correct content type for feeds. $type = $this->query_vars['feed']; if ( 'feed' === $this->query_vars['feed'] ) { $type = get_default_feed(); } $headers['Content-Type'] = feed_content_type( $type ) . '; charset=' . get_option( 'blog_charset' ); // We're showing a feed, so WP is indeed the only thing that last changed. if ( ! empty( $this->query_vars['withcomments'] ) || str_contains( $this->query_vars['feed'], 'comments-' ) || ( empty( $this->query_vars['withoutcomments'] ) && ( ! empty( $this->query_vars['p'] ) || ! empty( $this->query_vars['name'] ) || ! empty( $this->query_vars['page_id'] ) || ! empty( $this->query_vars['pagename'] ) || ! empty( $this->query_vars['attachment'] ) || ! empty( $this->query_vars['attachment_id'] ) ) ) ) { $wp_last_modified_post = mysql2date( $date_format, get_lastpostmodified( 'GMT' ), false ); $wp_last_modified_comment = mysql2date( $date_format, get_lastcommentmodified( 'GMT' ), false ); if ( strtotime( $wp_last_modified_post ) > strtotime( $wp_last_modified_comment ) ) { $wp_last_modified = $wp_last_modified_post; } else { $wp_last_modified = $wp_last_modified_comment; } } else { $wp_last_modified = mysql2date( $date_format, get_lastpostmodified( 'GMT' ), false ); } if ( ! $wp_last_modified ) { $wp_last_modified = gmdate( $date_format ); } $wp_last_modified .= ' GMT'; $wp_etag = '"' . md5( $wp_last_modified ) . '"'; $headers['Last-Modified'] = $wp_last_modified; $headers['ETag'] = $wp_etag; // Support for conditional GET. if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) {Introduced in 2.0.0. Unchanged from 6.7.7 through 7.1.0.
Signature, return type and hooks compared across 5 parsed releases.
src/wp-includes/class-wp.php, and regenerated for each WordPress release so it tracks the code rather than a snapshot of it.