Make WordPress Core

Opened 10 months ago

Closed 3 weeks ago

Last modified 3 weeks ago

#59043 closed defect (bug) (fixed)

the_excerpt() function return excerpt with different length in page load and ajax request on WordPress 6.3

Reported by: sarathlal's profile sarathlal Owned by: swissspidy's profile swissspidy
Milestone: 6.6 Priority: normal
Severity: normal Version: 6.3
Component: Editor Keywords: has-patch has-unit-tests commit
Focuses: rest-api Cc:

Description

I'm using the_excerpt() function in a post loop that display posts from a custom WP query for "Post" post type. Using the same query with pagination parameter and same post loop to display more posts using AJAX.

Recently I noticed that the excerpt length was different on page load & AJAX load. I think, it's started after updating to WordPress 6.3. Another user also raised a related support thread in support forum.

https://wordpress.org/support/topic/unexpected-behavior-in-post-loop-after-sending-ajax-request/

Attachments (2)

59043.diff (533 bytes) - added by khokansardar 10 months ago.
59043.2.diff (1.1 KB) - added by rajinsharwar 9 months ago.
Checking for AJAX calls

Download all attachments as: .zip

Change History (46)

#1 @sarathlal
10 months ago

#59044 was marked as a duplicate.

This ticket was mentioned in PR #4988 on WordPress/wordpress-develop by @khokansardar.


10 months ago
#2

  • Keywords has-patch added

This PR fix the excerpt length related issues in frontend

Trac ticket: #59043

@khokansardar
10 months ago

#3 @khokansardar
10 months ago

  • Keywords needs-testing added

@sarathlal Please check with above patch, it should resolve the issues related excerpt.

#4 @sarathlal
10 months ago

@khokansardar Yes. The update resolved excerpt related issue. I have verified that.

#5 @poena
10 months ago

  • Keywords needs-testing-info added

Hi
Thank you for the report.

Kindly include exact step-by-step instructions for:

  • How to reproduce the problem.
  • How to test the patch, including how to confirm that both the excerpt block, all the excerpt block's settings, and the use of the_excerpt in classic themes are working correctly.

It is difficult for testers to test an issue without more information about your custom code, as the tester would need to make a lot of assumptions about both the query and how you implemented the Ajax call, that may or may not affect the test result.

#6 @sarathlal
10 months ago

@poena Sure.

Here are the custom code I'm using in my theme.

* Code in home page template.

<?php
$current_page = get_query_var('paged');
$per_page = get_option('posts_per_page');
$offset = $current_page > 0 ? $per_page * ($current_page-1) : 0;

$blog_args = array(
    'post_type' => 'post',
    'post_status' => 'publish',     
    'posts_per_page' => $per_page,
    'offset' => $offset,
    'order'=>'DESC',
    'orderby'=>'date',
);
$blogs_query = new WP_Query($blog_args);

if ( $blogs_query->have_posts() ) { ?>
        <section class="py-3">
                <div class="container">
                        <p class="section-title">Other Blogs</p>

                        <div id="th-blog-list" class="row posts-grid gx-5 gy-4">
                                <?php 
                while ($blogs_query->have_posts()) : $blogs_query->the_post();
                    $post_id   = get_the_ID();
                    ?>
                    <?php get_template_part('template-parts/post-card'); ?>
                                <?php endwhile; ?>
                        </div>

                        <?php
                        $data = array(
                                'paged' => 2,
                                '_wpnonce' => wp_create_nonce('load-posts'),
                        );
                        global $wp_query;
                        if(isset( $blogs_query->max_num_pages ) && ($blogs_query->max_num_pages > 1)){ ?>
                                <div class="row mb-4 mt-4">
                                        <div class="col-12 text-center">
                                                <span class="spinner-border spinner-border-sm" style="display:none" id="btn-loader" role="status" aria-hidden="true"></span>
                                                <?php
                                                echo '<button type="button" class="btn btn-dark ps-4 pt-2 pe-4 pb-2" id="load-more" '. implode(' ', array_map(
                                                    function ($k, $v) { return "data-" . $k .'="'. htmlspecialchars($v) .'"'; },
                                                    array_keys($data), $data
                                                )) .' />Show more</button>';
                                                ?>
                                        </div>                                          
                                </div>                  
                        <?php } ?>

                </div>
        </section>
<?php } ?>
<?php wp_reset_postdata(); ?>

* Code in template-parts/post-card.php

<?php
/**
 * Template part for displaying blog post card
 *
 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
 *
 */
?>

<div class="col-sm-12 col-md-6">
    <div class="post-item">
            <a href="<?php echo get_permalink(get_the_ID()); ?>">
                    <?php echo get_the_post_thumbnail( get_the_ID(), 'full', array( 'class' => 'f-img' ) ); ?>
            </a>
            <p class="title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></p>
            <p class="short-desc"><?php the_excerpt(); ?></p>
            <p class="author"><?php echo get_the_author(); ?> | <?php echo get_the_date('F j, Y', get_the_ID()); ?></p>
    </div>
</div>

* Code in functions.php for AJAX load more

function th37t_load_more_posts() {
        $req = $_REQUEST;

        if(!isset($_REQUEST['_wpnonce'])){
                 die('stop');
        }

        if(!(wp_verify_nonce( $_REQUEST['_wpnonce'], 'load-posts'))){
                die('stop');
        }

        unset($req["action"]);
        unset($req["_wpnonce"]);

        $per_page = get_option('posts_per_page');
        $default_data = array(
                'post_type' => 'post',
                'posts_per_page' => $per_page,
                'post_status' => 'publish',
                'order'=>'DESC',
                'orderby'=>'date',
        );
        $args = array_merge($default_data, $req);

        $ajaxposts = new WP_Query($args);

        $html = '';
        if($ajaxposts->have_posts()) {
                while($ajaxposts->have_posts()) : $ajaxposts->the_post();
                    ob_start();
                    get_template_part('template-parts/post-card');
                    $html .= ob_get_contents();
                    ob_end_clean();

                endwhile;
        }

        $next_page = false;
        $max_pages = isset($ajaxposts->max_num_pages) ? $ajaxposts->max_num_pages : false;

        $current_page = isset($_REQUEST['paged']) ? $_REQUEST['paged'] : false;

        if( $current_page && $max_pages && $current_page < $max_pages ){
                $next_page = $current_page + 1;
        }
        
        $data = array(
                'html' => $html,
                'next_page' => $next_page,
        );

        wp_send_json($data);
        exit;
}
add_action('wp_ajax_th_load_more_posts', 'th37t_load_more_posts');
add_action('wp_ajax_nopriv_th_load_more_posts', 'th37t_load_more_posts');

* Jquery code for "Load more" button

(function($){
	$('#load-more').on('click', function() {
		$('#btn-loader').show();
		$('#load-more').attr("disabled", true);
		var data = $(this).data();

		data['action'] = 'th_load_more_posts';

		$.ajax({
			type: 'POST',
			url: "<?php echo admin_url('admin-ajax.php'); ?>",
			dataType: 'JSON',
			data: data,
			success: function (res) {
				$('#btn-loader').hide();
				if(res.html){
					$('#th-blog-list').append(res.html);
				}

				if(res.next_page){
					$('#load-more').data('paged', res.next_page);
					$('#load-more').attr("disabled", false);
				}else{
					$('#load-more').attr("disabled", true);
				}
			}
		});
	});
})(jQuery);

Now on initial page load, the excerpt length will be 50 words if there are no any filter. But when I click on "Load More" button, the length will be 100 words.

I'm little confused about why the code in WordPress block affect core functionality?

#7 @Otto42
10 months ago

  • Keywords needs-patch added; has-patch removed
  • Priority changed from normal to high
  • Severity changed from normal to major

Confirmed, this is a problem in core.

Added here: https://core.trac.wordpress.org/changeset/56065#file90

It seems that the intention was to add a setting somewhere to allow the length to be modified in the editor. But there is no setting that I can find, and in any case, such a setting should not exist if the theme or a plugin is overriding the excerpt length manually.

The given patch is not the correct way to fix it. It works technically, but only through accidental means.

#8 @SergeyBiryukov
10 months ago

  • Milestone changed from Awaiting Review to 6.3.1

#9 @audrasjb
9 months ago

#59064 was marked as a duplicate.

#10 @swissspidy
9 months ago

  • Component changed from Posts, Post Types to Editor

The given patch is not the correct way to fix it. It works technically, but only through accidental means.

Agreed. It only works because is_admin() and REST_REQUEST are never both true at the same time, so the filter will never ever be applied.

In any case, this needs to be fixed in Gutenberg as the source of truth is there:
Also, any changes to this file need to be done in Gutenberg: https://github.com/WordPress/gutenberg/blob/d5d8533cf2cc04bb005bda147114cf00782d6c38/packages/block-library/src/post-excerpt/index.php#L80-L95

#12 @rajinsharwar
9 months ago

Added a fix PR as well there, can anyone take a look?
https://github.com/WordPress/gutenberg/pull/53571

@rajinsharwar
9 months ago

Checking for AJAX calls

#13 @rajinsharwar
9 months ago

  • Keywords has-patch added; needs-testing-info needs-patch removed

This ticket was mentioned in Slack in #core by audrasjb. View the logs.


9 months ago

#15 follow-up: @audrasjb
9 months ago

  • Milestone changed from 6.3.1 to 6.3.2

WP 6.3.1 is going to be released in the next few days, so let's move this ticket to 6.3.2 to give it more time to be committed and backported.

#16 @SergeyBiryukov
9 months ago

If anyone is looking for a workaround, this should restore the excerpt length to whatever it was set before it is altered in blocks/post-excerpt.php:

function wp59043_restore_excerpt_length() {
	global $wp_filter;

	unset( $wp_filter['excerpt_length']->callbacks[ PHP_INT_MAX ] );
}
add_action( 'init', 'wp59043_restore_excerpt_length' );

#17 in reply to: ↑ 15 @dcooney
8 months ago

Replying to audrasjb:

WP 6.3.1 is going to be released in the next few days, so let's move this ticket to 6.3.2 to give it more time to be committed and backported.

Without realizing this was a duplicate, I went with a different approach to solve this same issue by also checking for ! wp_doing_ajax().
https://github.com/WordPress/wordpress-develop/pull/5217

#18 @SergeyBiryukov
8 months ago

#59350 was marked as a duplicate.

This ticket was mentioned in Slack in #core-editor by fabiankaegy. View the logs.


8 months ago

This ticket was mentioned in Slack in #core by joemcgill. View the logs.


8 months ago

#21 @joemcgill
8 months ago

  • Milestone changed from 6.3.2 to 6.4

This likely needs to be tested in the Gutenberg plugin. Given the lack of movement on this, I'm going to move it to the 6.4 milestone.

#22 @swissspidy
8 months ago

#59504 was marked as a duplicate.

#24 @hellofromTonya
8 months ago

  • Milestone 6.4 deleted
  • Resolution set to reported-upstream
  • Status changed from new to closed

Hello @sarathlal,

Welcome to Core's Trace. Thank you for reporting this issue :)

The issue was introduced in [56065], as @Otto42 found here.

Fixing it needs to happen in the Gutenberg repo. Why? The bug is within the packages that Gutenberg maintains and Core consumes (meaning Core cannot directly change the package source code to fix the bug).

What's the process to get it resolved?

  1. It needs to be reported upstream in the Gutenberg repository. ✅

https://github.com/WordPress/gutenberg/issues/53570 was opened (thank you @rajinsharwar) to report it.

  1. Discussion should shift to that issue rather than here in Trac.
  1. Possible fixes and testing should be proposed as a Pull Request (PR) in Gutenberg and linked to its issue (not this Trac ticket).
  1. Once the fix is merged into Gutenberg, then it gets cherry picked for a release branch.
  1. Then the packages are published in Gutenberg, which would include the fix.
  1. Then a patch is submitted and committed that updates Core's package.json package versions.

To help, I'll update the Gutenberg issue with the keywords, add the issue to the project board, and leave comment on the issue.

As there's no further actions that can happen here, I'm closing this ticket as reported-upstream. Let's shift the discussions, iteration, and testing to the issue in Gutenberg.

Thank you everyone for your contributions :)

#25 @hellofromTonya
8 months ago

Following up:
I've taken the following actions in the upstream issue:

  1. Marked it as high priority.
  2. Moved it into the 6.4 Editor Tasks project board, where the 6.4 Editor leads track issues.
  3. Left a comment that shares context and reasoning.
  4. Talked to a colleague who has volunteered to help test and move a fix forward.
  5. Pinged the 6.4 Editor Triage Leads for their awareness and asked to include the fix in package updates if it lands before 6.4 RC1.

#26 @antonvlasenko
7 months ago

This ticket can be moved forward by code reviewing and testing https://github.com/WordPress/gutenberg/pull/55400, which I believe is the correct solution.

#27 @swissspidy
7 months ago

#59714 was marked as a duplicate.

@azaozz commented on PR #5932:


4 months ago
#31

The code seems okay but I'm still unsure why this has to work in this particular way. Generally this change makes it so sometimes the excerpt length may be overridden with an arbitrary value when doing a REST request. Couple of thoughts about that:

  • Seems the initial need was for a hard-coded override of 100 that is only for use in the editor and is later trimmed from JS. Not really sure why this was changed to an arbitrary value as that doesn't seem needed?
  • The change seems implemented in a somewhat strange way by adding a non-removable filter callback. As it is still a filter, the excerpt length value can be overridden by adding another filter callback that runs after this one. However is seems that the block editor is (somewhat) hard-coded to expect the exact length of 100 words for the excerpt in edit context, so allowing that to be overridden doesn't seem like a particularly good idea?

How about instead of adding that non-removable filter callback we add another param like $override_length = null to wp_trim_excerpt() and pass that through the 'get_the_excerpt' filter that's used right underneath? So

$excerpt = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post );

would become:

$excerpt = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post, (int) $override_excerpt_length );

where $override_excerpt_length may actually be better off as a hard-coded 100. Untested, but thinking this would work perhaps a bit better? At least will enforce the length when set through the REST API and perhaps prevent "strange" accidental edge cases in the future if the excerpt_length filter is used by a plugin that "must enforce its own setting at all costs" :)

@swissspidy commented on PR #5932:


4 months ago
#32

However is seems that the block editor is (somewhat) hard-coded to expect the exact length of 100 words for the excerpt in edit context [...]

How did you get to this conclusion, where did you get this number from? 🤔

The whole issue right now is that Gutenberg offers a way to fully customize the excerpt length in the editor, but that length is not actually honored in the REST API response it receives.
https://i0.wp.com/github.com/WordPress/wordpress-develop/assets/841956/c3b3bc21-0405-4fb6-ad57-65d0775804ba

This PR is an attempt to solve this in a less hacky way than https://github.com/WordPress/gutenberg/pull/55400

As it is still a filter, the excerpt length value can be overridden by adding another filter callback that runs after this one.

Theoretically, yes. With PHP_INT_MAX this risk is greatly reduced though.

But plugins can also completely override the excerpt using the get_the_excerpt filter anyway. There's no safeguard for that.

How about instead of adding that non-removable filter callback we add another param like $override_length = null to wp_trim_excerpt() and pass that through the 'get_the_excerpt' filter that's used right underneath? So

At least will enforce the length when set through the REST API and perhaps prevent "strange" accidental edge cases in the future if the excerpt_length filter is used by a plugin that "must enforce its own setting at all costs" :)

This approach does not prevent a plugin from filtering get_the_excerpt and enforcing its own setting at all costs.

So in this regard, both approaches have the same problem and I don't think it's solvable. Plugins will always be able to filter the excerpt.

@azaozz commented on PR #5932:


4 months ago
#33

How did you get to this conclusion, where did you get this number from? 🤔

The initial bug report, here, and the initial patch? :)

The whole issue right now is that Gutenberg offers a way to fully customize the excerpt length in the editor, but that length is not actually honored in the REST API response it receives.

May be missing something but how would that slider work if the excerpt was too short? Lets say the user sets the excerpt length to 20, how can they increase it next time they edit the post? Afaik this is the problem here, the editor needs a consistent excerpt length (100?) that can be trimmed on display to allow the user to change it (and actually see it).

This approach does not prevent a plugin from filtering get_the_excerpt

Right. But that most likely would be a deliberate change targeting exactly this functionality. I'm thinking more about accidental overrides by plugins that do filters with PHP_INT_MAX, etc.

@swissspidy commented on PR #5932:


4 months ago
#34

The initial bug report, https://github.com/WordPress/gutenberg/issues/53570, and the initial patch? :)

There was no _need_ for a _hardcoded_ excerpt length. That was simply a hacky workaround to provide something to the editor to work with. 100 is the highest number that can be chosen in the editor. No proper solution was implemented which actually returns the actually configured length.
So I am offering this PR to fix that, as it allows trimming excerpts at the desired maximum length.

May be missing something but how would that slider work if the excerpt was too short?

Just like the excerpt_length filter, the whole setting in the editor is a _maximum_ excerpt length. If your post_content is shorter, you'll get a shorter excerpt.

Lets say the user sets the excerpt length to 20, how can they increase it next time they edit the post?

The excerpt block is used in places like Query Loop blocks. If you want to increase the excerpt length in places where you used the block, go edit the block.

But questions about the excerpt block are better target at someone who built the block :)
I'm merely offering help here to fix a bad implementation.

Afaik this is the problem here, the editor needs a consistent excerpt length (100?) that can be trimmed on display to allow the user to change it (and actually see it).

What the editor needs is a way to trim the excerpt length to the desired maximum and this change here allows it to do so. At https://github.com/WordPress/gutenberg/pull/55400#issuecomment-1905838055 I explained why it's better to do the trimming server-side rather than client-side.

@andraganescu commented on PR #5932:


4 months ago
#35

We also need a Gutenberg PR to use this new param.

@swissspidy commented on PR #5932:


4 months ago
#38

@azaozz I would appreciate another review

If we can merge this now into core there is still time to get the corresponding change into this week's Gutenberg RC so we can finally fix this in 6.5

#39 @swissspidy
3 months ago

  • Milestone set to 6.6
  • Resolution reported-upstream deleted
  • Status changed from closed to reopened

Reopening as it likely requires a core change.

#40 @swissspidy
2 months ago

  • Priority changed from high to normal
  • Severity changed from major to normal

#41 @swissspidy
6 weeks ago

  • Focuses rest-api added

This ticket was mentioned in Slack in #core-restapi by swissspidy. View the logs.


6 weeks ago

@TimothyBlynJacobs commented on PR #5932:


6 weeks ago
#43

This is a solution that I don't love, but can't think of anything better.

The reason I don't love it, is it feels different to how we handle everything else that is variable (like image sizes, avatar sizes, raw vs rendered) where the API spits out multiple choices, and the client chooses the one that is closest to their desired size. I think this is the first time we're giving the client control over a specific API property like that.

But that all being said, I can't really think of a better way of handling this. And I think this can be thought of as an extension to things like ?_fields.

@andraganescu commented on PR #5932:


5 weeks ago
#44

With the latest thumbs up this is definitely ready IMO :)

#45 @swissspidy
5 weeks ago

  • Keywords commit added; needs-testing removed
  • Owner set to swissspidy
  • Status changed from reopened to accepted

Tentatively marking as commit candidate.

Once this is in core, a corresponding change would need to be made in Gutenberg as well.

#46 @swissspidy
3 weeks ago

  • Resolution set to fixed
  • Status changed from accepted to closed

In 58065:

REST API: allow overriding excerpt length.

This can be used by the excerpt block in the editor to change the excerpt length without filtering excerpt_length in a conflicting way. This enhancement still needs a corresponding change on the Gutenberg side.

Props swissspidy, antonvlasenko, mukesh27, azaozz, andraganescu, timothyblynjacobs.
Fixes #59043.

Note: See TracTickets for help on using tickets.