Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/wp-includes/class-walker-comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,19 @@ public function start_el( &$output, $data_object, $depth = 0, $args = array(), $
return;
}

/*
* Allow a registered comment type to render itself. An explicit `callback`
* argument passed to wp_list_comments() takes precedence and is handled above.
*/
$comment_type_object = get_comment_type_object( $comment->comment_type );

if ( $comment_type_object && is_callable( $comment_type_object->render_callback ) ) {
ob_start();
call_user_func( $comment_type_object->render_callback, $comment, $args, $depth );
$output .= ob_get_clean();
return;
}

if ( 'comment' === $comment->comment_type ) {
add_filter( 'comment_text', array( $this, 'filter_comment_text' ), 40, 2 );
}
Expand Down
28 changes: 22 additions & 6 deletions src/wp-includes/class-wp-comment-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ final class WP_Comment_Type {
*/
public $_builtin = false;

/**
* Callback used to render a comment of this type in comment lists.
*
* When set to a callable, {@see Walker_Comment} invokes it to render a
* comment of this type, receiving the same arguments as the `callback`
* argument of wp_list_comments(): the comment object, the arguments array,
* and the depth. An explicit `callback` passed to wp_list_comments() still
* takes precedence. Output from the callback is printed unescaped; the
* callback is responsible for escaping all output. Default null.
*
* @since 7.1.0
* @var callable|null
*/
public $render_callback = null;

/**
* Whether the comment type is hierarchical.
*
Expand Down Expand Up @@ -187,12 +202,13 @@ public function set_props( $args ) {
* treated as a provided value and overwrite the default name with false.
*/
$defaults = array(
'labels' => array(),
'description' => '',
'public' => true,
'internal' => false,
'show_ui' => null,
'_builtin' => false,
'labels' => array(),
'description' => '',
'public' => true,
'internal' => false,
'show_ui' => null,
'render_callback' => null,
'_builtin' => false,
);

$args = array_merge( $defaults, $args );
Expand Down
10 changes: 8 additions & 2 deletions src/wp-includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,14 @@ function create_initial_comment_types() {
* the admin interface or by front-end users. Default true.
* @type bool $internal Whether the comment type is for internal use only and should be
* excluded from default public-facing contexts. Default false.
* @type bool $show_ui Whether to generate and allow a UI for managing this comment type
* in the admin. Default is value of $public.
* @type bool $show_ui Whether to generate and allow a UI for managing this comment
* type in the admin. Default is value of $public.
* @type callable $render_callback Callback used to render a comment of this type in comment
* lists. Receives the same arguments as the `callback` argument
* of wp_list_comments() (the comment, the arguments, and the
* depth). An explicit wp_list_comments() `callback` takes
* precedence. Output is printed unescaped; the callback must
* escape all output. Default null.
* }
* @return WP_Comment_Type|WP_Error The registered comment type object on success,
* WP_Error object on failure.
Expand Down
179 changes: 179 additions & 0 deletions tests/phpunit/tests/comment/walker.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,185 @@ public function test_has_children() {
array( $comment_child, $comment_parent )
);
}

/**
* Renders the comments on the test post and returns the markup.
*
* @param array $comments Comments to render.
* @return string Rendered markup.
*/
private function render_comments( $comments, $args = array() ) {
return wp_list_comments(
array_merge(
array(
'echo' => false,
'walker' => new Walker_Comment(),
),
$args
),
$comments
);
}

/**
* A registered comment type's render_callback is used to render its comments.
*
* @ticket 35214
*/
public function test_render_callback_renders_registered_comment_type() {
register_comment_type(
'review',
array(
'render_callback' => static function ( $comment ) {
echo '<li class="review">' . esc_html( $comment->comment_content ) . '</li>';
},
)
);

$comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => $this->post_id,
'comment_type' => 'review',
'comment_content' => 'Rendered by callback',
'comment_approved' => '1',
)
);

$output = $this->render_comments( array( get_comment( $comment_id ) ) );

$this->assertStringContainsString( 'class="review"', $output );
$this->assertStringContainsString( 'Rendered by callback', $output );

unregister_comment_type( 'review' );
}

/**
* An explicit wp_list_comments() callback takes precedence over a type's render_callback.
*
* @ticket 35214
*/
public function test_explicit_callback_takes_precedence_over_render_callback() {
register_comment_type(
'review',
array(
'render_callback' => static function () {
echo 'FROM_TYPE_CALLBACK';
},
)
);

$comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => $this->post_id,
'comment_type' => 'review',
'comment_approved' => '1',
)
);

$output = $this->render_comments(
array( get_comment( $comment_id ) ),
array(
'callback' => static function () {
echo 'FROM_LIST_CALLBACK';
},
)
);

$this->assertStringContainsString( 'FROM_LIST_CALLBACK', $output );
$this->assertStringNotContainsString( 'FROM_TYPE_CALLBACK', $output );

unregister_comment_type( 'review' );
}

/**
* Built-in comment types without a render_callback render normally.
*
* @ticket 35214
*/
public function test_comment_type_without_render_callback_renders_normally() {
$comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => $this->post_id,
'comment_type' => 'comment',
'comment_content' => 'A normal comment',
'comment_approved' => '1',
)
);

$output = $this->render_comments( array( get_comment( $comment_id ) ) );

$this->assertStringContainsString( 'A normal comment', $output );
}

/**
* The render_callback receives the comment, the arguments array, and the depth,
* matching the wp_list_comments() `callback` contract.
*
* @ticket 35214
*/
public function test_render_callback_receives_comment_args_and_depth() {
$received = array();

register_comment_type(
'review',
array(
'render_callback' => static function ( $comment, $args, $depth ) use ( &$received ) {
$received = array(
'comment' => $comment,
'args' => $args,
'depth' => $depth,
);
echo '<li></li>';
},
)
);

$comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => $this->post_id,
'comment_type' => 'review',
'comment_approved' => '1',
)
);

$this->render_comments( array( get_comment( $comment_id ) ) );

$this->assertInstanceOf( 'WP_Comment', $received['comment'], 'The callback should receive the comment object.' );
$this->assertSame( (string) $comment_id, (string) $received['comment']->comment_ID, 'The callback should receive the rendered comment.' );
$this->assertIsArray( $received['args'], 'The callback should receive the arguments array.' );
$this->assertSame( 1, $received['depth'], 'A top-level comment should be rendered at depth 1.' );

unregister_comment_type( 'review' );
}

/**
* A render_callback that is not callable is ignored and the comment renders normally.
*
* @ticket 35214
*/
public function test_non_callable_render_callback_is_ignored() {
register_comment_type(
'review',
array(
'render_callback' => 'this_is_not_a_callable_function',
)
);

$comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => $this->post_id,
'comment_type' => 'review',
'comment_content' => 'Falls back to normal rendering',
'comment_approved' => '1',
)
);

$output = $this->render_comments( array( get_comment( $comment_id ) ) );

$this->assertStringContainsString( 'Falls back to normal rendering', $output );

unregister_comment_type( 'review' );
}
}

class Comment_Callback_Test_Helper {
Expand Down
13 changes: 13 additions & 0 deletions tests/phpunit/tests/comment/wpCommentType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ public function test_instance_defaults() {
$this->assertFalse( $comment_type->_builtin );
$this->assertTrue( $comment_type->show_ui );
$this->assertFalse( $comment_type->hierarchical );
$this->assertNull( $comment_type->render_callback );
}

/**
* @ticket 35214
*
* @covers ::set_props
*/
public function test_render_callback_is_stored() {
$callback = static function () {};
$comment_type = new WP_Comment_Type( 'foo', array( 'render_callback' => $callback ) );

$this->assertSame( $callback, $comment_type->render_callback );
}

/**
Expand Down
Loading