Posts: Avoid the use of globals in get_the_content() and related functions.

This changeset introduces `$post` parameters to `get_the_content()` and
`wp_trim_excerpt()`. When a `$post` object is passed to one of these functions,
the functions will operate on the data from that object, rather than from the
post globals (`$authordata`, `$page`, etc). This ensures that the functions work
in a predictable manner when used outside of the regular post loop.

The global-mismatch problem is surfaced in cases where `get_the_excerpt()` is
called outside of the post loop, on posts that don't have a defined excerpt. In
these cases, the post globals - used to generate a fallback excerpt - may refer
to the incorrect object, resulting in PHP notices or other unpredictable
behavior. See #36934 for a related issue.

Props spacedmonkey, kraftbj, Shital Patel.
Fixes #42814.
Built from https://develop.svn.wordpress.org/trunk@44941


git-svn-id: http://core.svn.wordpress.org/trunk@44772 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Boone Gorges 2019-03-20 15:49:49 +00:00
parent 8baa737840
commit 05fa2363ab
6 changed files with 100 additions and 21 deletions

View File

@ -4155,6 +4155,42 @@ class WP_Query {
return; return;
} }
$elements = $this->generate_postdata( $post );
if ( false === $elements ) {
return;
}
$id = $elements['id'];
$authordata = $elements['authordata'];
$currentday = $elements['currentday'];
$currentmonth = $elements['currentmonth'];
$page = $elements['page'];
$pages = $elements['pages'];
$multipage = $elements['multipage'];
$more = $elements['more'];
$numpages = $elements['numpages'];
return true;
}
/**
* Generate post data.
*
* @since 5.2.0
*
* @param WP_Post|object|int $post WP_Post instance or Post ID/object.
* @return array|bool $elements Elements of post or false on failure.
*/
public function generate_postdata( $post ) {
if ( ! ( $post instanceof WP_Post ) ) {
$post = get_post( $post );
}
if ( ! $post ) {
return false;
}
$id = (int) $post->ID; $id = (int) $post->ID;
$authordata = get_userdata( $post->post_author ); $authordata = get_userdata( $post->post_author );
@ -4235,7 +4271,9 @@ class WP_Query {
*/ */
do_action_ref_array( 'the_post', array( &$post, &$this ) ); do_action_ref_array( 'the_post', array( &$post, &$this ) );
return true; $elements = compact( 'id', 'authordata', 'currentday', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages' );
return $elements;
} }
/** /**
* After looping through a nested query, this function * After looping through a nested query, this function

View File

@ -182,7 +182,7 @@ add_filter( 'the_excerpt', 'convert_smilies' );
add_filter( 'the_excerpt', 'convert_chars' ); add_filter( 'the_excerpt', 'convert_chars' );
add_filter( 'the_excerpt', 'wpautop' ); add_filter( 'the_excerpt', 'wpautop' );
add_filter( 'the_excerpt', 'shortcode_unautop' ); add_filter( 'the_excerpt', 'shortcode_unautop' );
add_filter( 'get_the_excerpt', 'wp_trim_excerpt' ); add_filter( 'get_the_excerpt', 'wp_trim_excerpt', 10, 2 );
add_filter( 'the_post_thumbnail_caption', 'wptexturize' ); add_filter( 'the_post_thumbnail_caption', 'wptexturize' );
add_filter( 'the_post_thumbnail_caption', 'convert_smilies' ); add_filter( 'the_post_thumbnail_caption', 'convert_smilies' );

View File

@ -3678,14 +3678,17 @@ function human_time_diff( $from, $to = '' ) {
* The ' […]' string can be modified by plugins/themes using the {@see 'excerpt_more'} filter * The ' […]' string can be modified by plugins/themes using the {@see 'excerpt_more'} filter
* *
* @since 1.5.0 * @since 1.5.0
* @since 5.2.0 Added the `$post` parameter.
* *
* @param string $text Optional. The excerpt. If set to empty, an excerpt is generated. * @param string $text Optional. The excerpt. If set to empty, an excerpt is generated.
* @param WP_Post|object|int $post Optional. WP_Post instance or Post ID/object. Default is null.
* @return string The excerpt. * @return string The excerpt.
*/ */
function wp_trim_excerpt( $text = '' ) { function wp_trim_excerpt( $text = '', $post = null ) {
$raw_excerpt = $text; $raw_excerpt = $text;
if ( '' == $text ) { if ( '' == $text ) {
$text = get_the_content( '' ); $post = get_post( $post );
$text = get_the_content( '', false, $post );
$text = strip_shortcodes( $text ); $text = strip_shortcodes( $text );
$text = excerpt_remove_blocks( $text ); $text = excerpt_remove_blocks( $text );

View File

@ -253,6 +253,7 @@ function the_content( $more_link_text = null, $strip_teaser = false ) {
* Retrieve the post content. * Retrieve the post content.
* *
* @since 0.71 * @since 0.71
* @since 5.2.0 Added the `$post` parameter.
* *
* @global int $page Page number of a single post/page. * @global int $page Page number of a single post/page.
* @global int $more Boolean indicator for whether single post/page is being viewed. * @global int $more Boolean indicator for whether single post/page is being viewed.
@ -263,12 +264,23 @@ function the_content( $more_link_text = null, $strip_teaser = false ) {
* *
* @param string $more_link_text Optional. Content for when there is more text. * @param string $more_link_text Optional. Content for when there is more text.
* @param bool $strip_teaser Optional. Strip teaser content before the more text. Default is false. * @param bool $strip_teaser Optional. Strip teaser content before the more text. Default is false.
* @param WP_Post|object|int $post Optional. WP_Post instance or Post ID/object. Default is null.
* @return string * @return string
*/ */
function get_the_content( $more_link_text = null, $strip_teaser = false ) { function get_the_content( $more_link_text = null, $strip_teaser = false, $post = null ) {
global $page, $more, $preview, $pages, $multipage; global $page, $more, $preview, $pages, $multipage;
$post = get_post(); $_post = get_post( $post );
if ( ! ( $_post instanceof WP_Post ) ) {
return '';
}
if ( null === $post ) {
$elements = compact( 'page', 'more', 'preview', 'pages', 'multipage' );
} else {
$elements = generate_postdata( $_post );
}
if ( null === $more_link_text ) { if ( null === $more_link_text ) {
$more_link_text = sprintf( $more_link_text = sprintf(
@ -276,7 +288,12 @@ function get_the_content( $more_link_text = null, $strip_teaser = false ) {
sprintf( sprintf(
/* translators: %s: Name of current post */ /* translators: %s: Name of current post */
__( 'Continue reading %s' ), __( 'Continue reading %s' ),
the_title_attribute( array( 'echo' => false ) ) the_title_attribute(
array(
'echo' => false,
'post' => $_post,
)
)
), ),
__( '(more…)' ) __( '(more…)' )
); );
@ -286,15 +303,16 @@ function get_the_content( $more_link_text = null, $strip_teaser = false ) {
$has_teaser = false; $has_teaser = false;
// If post password required and it doesn't match the cookie. // If post password required and it doesn't match the cookie.
if ( post_password_required( $post ) ) { if ( post_password_required( $_post ) ) {
return get_the_password_form( $post ); return get_the_password_form( $_post );
} }
if ( $page > count( $pages ) ) { // if the requested page doesn't exist if ( $elements['page'] > count( $elements['pages'] ) ) { // if the requested page doesn't exist
$page = count( $pages ); // give them the highest numbered page that DOES exist $elements['page'] = count( $elements['pages'] ); // give them the highest numbered page that DOES exist
} }
$content = $pages[ $page - 1 ]; $page_no = $elements['page'];
$content = $elements['pages'][ $page_no - 1 ];
if ( preg_match( '/<!--more(.*?)?-->/', $content, $matches ) ) { if ( preg_match( '/<!--more(.*?)?-->/', $content, $matches ) ) {
$content = explode( $matches[0], $content, 2 ); $content = explode( $matches[0], $content, 2 );
if ( ! empty( $matches[1] ) && ! empty( $more_link_text ) ) { if ( ! empty( $matches[1] ) && ! empty( $more_link_text ) ) {
@ -306,21 +324,21 @@ function get_the_content( $more_link_text = null, $strip_teaser = false ) {
$content = array( $content ); $content = array( $content );
} }
if ( false !== strpos( $post->post_content, '<!--noteaser-->' ) && ( ! $multipage || $page == 1 ) ) { if ( false !== strpos( $_post->post_content, '<!--noteaser-->' ) && ( ! $elements['multipage'] || $elements['page'] == 1 ) ) {
$strip_teaser = true; $strip_teaser = true;
} }
$teaser = $content[0]; $teaser = $content[0];
if ( $more && $strip_teaser && $has_teaser ) { if ( $elements['more'] && $strip_teaser && $has_teaser ) {
$teaser = ''; $teaser = '';
} }
$output .= $teaser; $output .= $teaser;
if ( count( $content ) > 1 ) { if ( count( $content ) > 1 ) {
if ( $more ) { if ( $elements['more'] ) {
$output .= '<span id="more-' . $post->ID . '"></span>' . $content[1]; $output .= '<span id="more-' . $_post->ID . '"></span>' . $content[1];
} else { } else {
if ( ! empty( $more_link_text ) ) { if ( ! empty( $more_link_text ) ) {
@ -332,7 +350,7 @@ function get_the_content( $more_link_text = null, $strip_teaser = false ) {
* @param string $more_link_element Read More link element. * @param string $more_link_element Read More link element.
* @param string $more_link_text Read More text. * @param string $more_link_text Read More text.
*/ */
$output .= apply_filters( 'the_content_more_link', ' <a href="' . get_permalink() . "#more-{$post->ID}\" class=\"more-link\">$more_link_text</a>", $more_link_text ); $output .= apply_filters( 'the_content_more_link', ' <a href="' . get_permalink( $_post ) . "#more-{$_post->ID}\" class=\"more-link\">$more_link_text</a>", $more_link_text );
} }
$output = force_balance_tags( $output ); $output = force_balance_tags( $output );
} }

View File

@ -1111,3 +1111,23 @@ function setup_postdata( $post ) {
return false; return false;
} }
/**
* Generates post data.
*
* @since 5.2.0
*
* @global WP_Query $wp_query Global WP_Query instance.
*
* @param WP_Post|object|int $post WP_Post instance or Post ID/object.
* @return array|bool Elements of post, or false on failure.
*/
function generate_postdata( $post ) {
global $wp_query;
if ( ! empty( $wp_query ) && $wp_query instanceof WP_Query ) {
return $wp_query->generate_postdata( $post );
}
return false;
}

View File

@ -13,7 +13,7 @@
* *
* @global string $wp_version * @global string $wp_version
*/ */
$wp_version = '5.2-alpha-44940'; $wp_version = '5.2-alpha-44941';
/** /**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.