WordPress.org

Make WordPress Core

Changeset 44941


Ignore:
Timestamp:
03/20/2019 03:48:46 PM (2 months ago)
Author:
boonebgorges
Message:

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.

Location:
trunk
Files:
2 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-query.php

    r44518 r44941  
    41564156        }
    41574157
     4158        $elements = $this->generate_postdata( $post );
     4159        if ( false === $elements ) {
     4160            return;
     4161        }
     4162
     4163        $id           = $elements['id'];
     4164        $authordata   = $elements['authordata'];
     4165        $currentday   = $elements['currentday'];
     4166        $currentmonth = $elements['currentmonth'];
     4167        $page         = $elements['page'];
     4168        $pages        = $elements['pages'];
     4169        $multipage    = $elements['multipage'];
     4170        $more         = $elements['more'];
     4171        $numpages     = $elements['numpages'];
     4172
     4173        return true;
     4174    }
     4175
     4176    /**
     4177     * Generate post data.
     4178     *
     4179     * @since 5.2.0
     4180     *
     4181     * @param WP_Post|object|int $post WP_Post instance or Post ID/object.
     4182     * @return array|bool $elements Elements of post or false on failure.
     4183     */
     4184    public function generate_postdata( $post ) {
     4185
     4186        if ( ! ( $post instanceof WP_Post ) ) {
     4187            $post = get_post( $post );
     4188        }
     4189
     4190        if ( ! $post ) {
     4191            return false;
     4192        }
     4193
    41584194        $id = (int) $post->ID;
    41594195
     
    42364272        do_action_ref_array( 'the_post', array( &$post, &$this ) );
    42374273
    4238         return true;
     4274        $elements = compact( 'id', 'authordata', 'currentday', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages' );
     4275
     4276        return $elements;
    42394277    }
    42404278    /**
  • trunk/src/wp-includes/default-filters.php

    r44714 r44941  
    183183add_filter( 'the_excerpt', 'wpautop' );
    184184add_filter( 'the_excerpt', 'shortcode_unautop' );
    185 add_filter( 'get_the_excerpt', 'wp_trim_excerpt' );
     185add_filter( 'get_the_excerpt', 'wp_trim_excerpt', 10, 2 );
    186186
    187187add_filter( 'the_post_thumbnail_caption', 'wptexturize' );
  • trunk/src/wp-includes/formatting.php

    r44858 r44941  
    36793679 *
    36803680 * @since 1.5.0
    3681  *
    3682  * @param string $text Optional. The excerpt. If set to empty, an excerpt is generated.
     3681 * @since 5.2.0 Added the `$post` parameter.
     3682 *
     3683 * @param string             $text Optional. The excerpt. If set to empty, an excerpt is generated.
     3684 * @param WP_Post|object|int $post Optional. WP_Post instance or Post ID/object. Default is null.
    36833685 * @return string The excerpt.
    36843686 */
    3685 function wp_trim_excerpt( $text = '' ) {
     3687function wp_trim_excerpt( $text = '', $post = null ) {
    36863688    $raw_excerpt = $text;
    36873689    if ( '' == $text ) {
    3688         $text = get_the_content( '' );
     3690        $post = get_post( $post );
     3691        $text = get_the_content( '', false, $post );
    36893692
    36903693        $text = strip_shortcodes( $text );
  • trunk/src/wp-includes/post-template.php

    r44471 r44941  
    254254 *
    255255 * @since 0.71
     256 * @since 5.2.0 Added the `$post` parameter.
    256257 *
    257258 * @global int   $page      Page number of a single post/page.
     
    262263 * @global int   $multipage Boolean indicator for whether multiple pages are in play.
    263264 *
    264  * @param string $more_link_text Optional. Content for when there is more text.
    265  * @param bool   $strip_teaser   Optional. Strip teaser content before the more text. Default is false.
     265 * @param string             $more_link_text Optional. Content for when there is more text.
     266 * @param bool               $strip_teaser   Optional. Strip teaser content before the more text. Default is false.
     267 * @param WP_Post|object|int $post           Optional. WP_Post instance or Post ID/object. Default is null.
    266268 * @return string
    267269 */
    268 function get_the_content( $more_link_text = null, $strip_teaser = false ) {
     270function get_the_content( $more_link_text = null, $strip_teaser = false, $post = null ) {
    269271    global $page, $more, $preview, $pages, $multipage;
    270272
    271     $post = get_post();
     273    $_post = get_post( $post );
     274
     275    if ( ! ( $_post instanceof WP_Post ) ) {
     276        return '';
     277    }
     278
     279    if ( null === $post ) {
     280        $elements = compact( 'page', 'more', 'preview', 'pages', 'multipage' );
     281    } else {
     282        $elements = generate_postdata( $_post );
     283    }
    272284
    273285    if ( null === $more_link_text ) {
     
    277289                /* translators: %s: Name of current post */
    278290                __( 'Continue reading %s' ),
    279                 the_title_attribute( array( 'echo' => false ) )
     291                the_title_attribute(
     292                    array(
     293                        'echo' => false,
     294                        'post' => $_post,
     295                    )
     296                )
    280297            ),
    281298            __( '(more…)' )
     
    287304
    288305    // If post password required and it doesn't match the cookie.
    289     if ( post_password_required( $post ) ) {
    290         return get_the_password_form( $post );
    291     }
    292 
    293     if ( $page > count( $pages ) ) { // if the requested page doesn't exist
    294         $page = count( $pages ); // give them the highest numbered page that DOES exist
    295     }
    296 
    297     $content = $pages[ $page - 1 ];
     306    if ( post_password_required( $_post ) ) {
     307        return get_the_password_form( $_post );
     308    }
     309
     310    if ( $elements['page'] > count( $elements['pages'] ) ) { // if the requested page doesn't exist
     311        $elements['page'] = count( $elements['pages'] ); // give them the highest numbered page that DOES exist
     312    }
     313
     314    $page_no = $elements['page'];
     315    $content = $elements['pages'][ $page_no - 1 ];
    298316    if ( preg_match( '/<!--more(.*?)?-->/', $content, $matches ) ) {
    299317        $content = explode( $matches[0], $content, 2 );
     
    307325    }
    308326
    309     if ( false !== strpos( $post->post_content, '<!--noteaser-->' ) && ( ! $multipage || $page == 1 ) ) {
     327    if ( false !== strpos( $_post->post_content, '<!--noteaser-->' ) && ( ! $elements['multipage'] || $elements['page'] == 1 ) ) {
    310328        $strip_teaser = true;
    311329    }
     
    313331    $teaser = $content[0];
    314332
    315     if ( $more && $strip_teaser && $has_teaser ) {
     333    if ( $elements['more'] && $strip_teaser && $has_teaser ) {
    316334        $teaser = '';
    317335    }
     
    320338
    321339    if ( count( $content ) > 1 ) {
    322         if ( $more ) {
    323             $output .= '<span id="more-' . $post->ID . '"></span>' . $content[1];
     340        if ( $elements['more'] ) {
     341            $output .= '<span id="more-' . $_post->ID . '"></span>' . $content[1];
    324342        } else {
    325343            if ( ! empty( $more_link_text ) ) {
     
    333351                 * @param string $more_link_text    Read More text.
    334352                 */
    335                 $output .= apply_filters( 'the_content_more_link', ' <a href="' . get_permalink() . "#more-{$post->ID}\" class=\"more-link\">$more_link_text</a>", $more_link_text );
     353                $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 );
    336354            }
    337355            $output = force_balance_tags( $output );
  • trunk/src/wp-includes/query.php

    r43571 r44941  
    11121112    return false;
    11131113}
     1114
     1115/**
     1116 * Generates post data.
     1117 *
     1118 * @since 5.2.0
     1119 *
     1120 * @global WP_Query $wp_query Global WP_Query instance.
     1121 *
     1122 * @param WP_Post|object|int $post WP_Post instance or Post ID/object.
     1123 * @return array|bool Elements of post, or false on failure.
     1124 */
     1125function generate_postdata( $post ) {
     1126    global $wp_query;
     1127
     1128    if ( ! empty( $wp_query ) && $wp_query instanceof WP_Query ) {
     1129        return $wp_query->generate_postdata( $post );
     1130    }
     1131
     1132    return false;
     1133}
  • trunk/tests/phpunit/tests/post/getTheExcerpt.php

    r42343 r44941  
    5858        $this->assertSame( 'Bar', get_the_excerpt( $post_id ) );
    5959    }
     60
     61    /**
     62     * @ticket 42814
     63     */
     64    public function test_should_fall_back_on_post_content_if_excerpt_is_empty_and_post_is_inferred_from_context() {
     65        $post_id = self::factory()->post->create(
     66            array(
     67                'post_content' => 'Foo',
     68                'post_excerpt' => '',
     69            )
     70        );
     71
     72        $q = new WP_Query(
     73            array(
     74                'p' => $post_id,
     75            )
     76        );
     77
     78        while ( $q->have_posts() ) {
     79            $q->the_post();
     80            $found = get_the_excerpt();
     81        }
     82
     83        $this->assertSame( 'Foo', $found );
     84    }
     85
     86    /**
     87     * @ticket 42814
     88     */
     89    public function test_should_fall_back_on_post_content_if_excerpt_is_empty_and_post_is_provided() {
     90        $GLOBALS['post'] = self::factory()->post->create_and_get(
     91            array(
     92                'post_content' => 'Foo',
     93                'post_excerpt' => '',
     94            )
     95        );
     96        $this->assertSame( 'Foo', get_the_excerpt( $GLOBALS['post'] ) );
     97    }
     98
     99    /**
     100     * @ticket 42814
     101     */
     102    public function test_should_respect_post_parameter_in_the_loop() {
     103        $p1 = self::factory()->post->create_and_get( array( 'post_excerpt' => 'Foo' ) );
     104        $p2 = self::factory()->post->create_and_get( array( 'post_excerpt' => 'Bar' ) );
     105        $q  = new WP_Query(
     106            array(
     107                'p' => $p1->ID,
     108            )
     109        );
     110
     111        while ( $q->have_posts() ) {
     112            $q->the_post();
     113            $found = get_the_excerpt( $p2 );
     114        }
     115
     116        $this->assertSame( 'Bar', $found );
     117    }
     118
     119    /**
     120     * @ticket 42814
     121     */
     122    public function test_should_respect_post_parameter_in_the_loop_when_falling_back_on_post_content() {
     123        $p1 = self::factory()->post->create_and_get(
     124            array(
     125                'post_content' => 'Foo',
     126                'post_excerpt' => '',
     127            )
     128        );
     129        $p2 = self::factory()->post->create_and_get(
     130            array(
     131                'post_content' => 'Bar',
     132                'post_excerpt' => '',
     133            )
     134        );
     135        $q  = new WP_Query(
     136            array(
     137                'p' => $p1->ID,
     138            )
     139        );
     140
     141        while ( $q->have_posts() ) {
     142            $q->the_post();
     143            $found = get_the_excerpt( $p2 );
     144        }
     145
     146        $this->assertSame( 'Bar', $found );
     147    }
    60148}
Note: See TracChangeset for help on using the changeset viewer.