Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for infinite loops in post.excerpt in specific cases #2972

Merged
merged 4 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 28 additions & 8 deletions src/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
*/
public static function build(WP_Post $wp_post): self
{
$post = new static();

Check failure on line 180 in src/Post.php

View workflow job for this annotation

GitHub Actions / PHP static analysis

Unsafe usage of new static().

$post->id = $wp_post->ID;
$post->ID = $wp_post->ID;
Expand Down Expand Up @@ -1215,25 +1215,26 @@
*
* @param int $page Optional. The page to show if the content of the post is split into multiple
* pages. Read more about this in the [Pagination Guide](https://timber.github.io/docs/v2/guides/pagination/#paged-content-within-a-post). Default `0`.
*
* @return string
* @param int $num_words Optional. The number of words to show. Default `-1` (show all).
* @param bool $remove_blocks Optional. Whether to remove blocks. Defaults to false. True when called from the $post->excerpt() method.
* @return string The content of the post.
*/
public function content($page = 0, $len = -1)
public function content($page = 0, $num_words = -1, $remove_blocks = false)
Levdbas marked this conversation as resolved.
Show resolved Hide resolved
{
if ($rd = $this->get_revised_data_from_method('content', [$page, $len])) {
if ($rd = $this->get_revised_data_from_method('content', [$page, $num_words])) {
return $rd;
}
if ($form = $this->maybe_show_password_form()) {
return $form;
}
if ($len == -1 && $page == 0 && $this->___content) {
if ($num_words == -1 && $page == 0 && $this->___content) {
return $this->___content;
}

$content = $this->post_content;

if ($len > 0) {
$content = \wp_trim_words($content, $len);
if ($num_words > 0) {
$content = \wp_trim_words($content, $num_words);
}

/**
Expand Down Expand Up @@ -1263,10 +1264,29 @@
}
}

/**
* Filters whether the content produced by block editor blocks should be removed or not from the content.
*
* If truthy then block whose content does not belong in the excerpt, will be removed.
* This removal is done using WordPress Core `excerpt_remove_blocks` function.
*
* @since 2.1.1
*
* @param bool $remove_blocks Whether blocks whose content should not be part of the excerpt should be removed
* or not from the excerpt.
*
* @see excerpt_remove_blocks() The WordPress Core function that will handle the block removal from the excerpt.
*/
$remove_blocks = (bool) \apply_filters('timber/post/content/remove_blocks', $remove_blocks);

if ($remove_blocks) {
$content = \excerpt_remove_blocks($content);
}

$content = $this->content_handle_no_teaser_block($content);
$content = \apply_filters('the_content', ($content));

if ($len == -1 && $page == 0) {
if ($num_words == -1 && $page == 0) {
$this->___content = $content;
}

Expand Down
2 changes: 1 addition & 1 deletion src/PostExcerpt.php
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ protected function run()

// Build an excerpt text from the post’s content.
if (empty($text)) {
$text = $this->post->content();
$text = $this->post->content(0, -1, true);
$text = TextHelper::remove_tags($text, $this->destroy_tags);
$text_before_trim = \trim($text);
$text_before_char_trim = '';
Expand Down
20 changes: 20 additions & 0 deletions tests/assets/block-tests/block-register.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

register_block_type('timber/test-block', [
'render_callback' => 'render_block_example',
'api_version' => 3,
'attributes' => [
'post_id' => [
'type' => 'integer',
],
],
]);

function render_block_example($attributes, $content = '', $wp_block = null)
{
// get the dynamically set post ID from the block attributes. We do it this way because get_the_ID() doesn't work in the block context during phpUnit tests.
return Timber::compile('block-template.twig', [
'post' => Timber::get_post($attributes['post_id']),
'attributes' => $attributes,
]);
}
6 changes: 6 additions & 0 deletions tests/assets/block-tests/block-template.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{ post.excerpt({
words: 100,
read_more: false
})
}}
<p>What is love</p>
29 changes: 29 additions & 0 deletions tests/test-timber-post-excerpt.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,33 @@ function ($defaults) {

$this->assertEquals('Let this be the content, albeit a very short one!&hellip;', (string) $excerpt);
}

/**
* Checks if the excerpt is correctly generated when the content contains a block.
* Prior to this fix, the excerpt would cause infinite loops (which show up as a segmentation fault in PHPunit).
*
* @ticket https://github.com/timber/timber/issues/2041
*
* @return void
*/
public function testExcerptWithCustomBlock()
{
require_once 'assets/block-tests/block-register.php';

// Create an empty post
$post_id = $this->factory->post->create([
'post_excerpt' => '',
'post_content' => '',
]);

// Update the post with a block and pass the post ID to the block
$this->factory->post->update_object($post_id, [
'post_excerpt' => '',
'post_content' => '<!-- wp:timber/test-block { "post_id": ' . $post_id . '} /--> Some other content',
]);

$post = Timber::get_post($post_id);

$this->assertEquals('Some other content', (string) $post->excerpt());
}
}