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
24 changes: 21 additions & 3 deletions src/wp-includes/class-walker-comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ public function display_element( $element, &$children_elements, $max_depth, $dep
* @since 5.9.0 Renamed `$comment` to `$data_object` and `$id` to `$current_object_id`
* to match parent class for PHP 8 named parameter support.
* @since 7.1.0 Comments of a registered comment type with a `render_callback`
* are rendered via that callback.
* are rendered via that callback, and short-ping rendering is
* driven by the comment type's `is_ping` property.
*
* @see Walker::start_el()
* @see wp_list_comments()
Expand Down Expand Up @@ -204,7 +205,9 @@ public function start_el( &$output, $data_object, $depth = 0, $args = array(), $
add_filter( 'comment_text', array( $this, 'filter_comment_text' ), 40, 2 );
}

if ( ( 'pingback' === $comment->comment_type || 'trackback' === $comment->comment_type ) && $args['short_ping'] ) {
$is_ping = $comment_type_object && $comment_type_object->is_ping;

if ( $is_ping && $args['short_ping'] ) {
ob_start();
$this->ping( $comment, $depth, $args );
$output .= ob_get_clean();
Expand Down Expand Up @@ -260,6 +263,7 @@ public function end_el( &$output, $data_object, $depth = 0, $args = array() ) {
* Outputs a pingback comment.
*
* @since 3.6.0
* @since 7.1.0 A registered, non-built-in ping type is labeled with its singular name.
*
* @see wp_list_comments()
*
Expand All @@ -269,10 +273,24 @@ public function end_el( &$output, $data_object, $depth = 0, $args = array() ) {
*/
protected function ping( $comment, $depth, $args ) {
$tag = ( 'div' === $args['style'] ) ? 'div' : 'li';

/*
* The built-in ping types share the 'Pingback:' label - trackbacks have carried it
* since 3.6 - so their markup is unchanged. A registered ping type would be
* mislabeled by it, so use its own singular name instead, matching comment_type().
*/
$comment_type_object = get_comment_type_object( $comment->comment_type );

if ( $comment_type_object && ! $comment_type_object->_builtin && isset( $comment_type_object->labels->singular_name ) ) {
/* translators: %s: Singular name of a registered comment type, e.g. "Webmention". */
$label = sprintf( __( '%s:' ), esc_html( $comment_type_object->labels->singular_name ) );
} else {
$label = __( 'Pingback:' );
}
?>
<<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( '', $comment ); ?>>
<div class="comment-body">
<?php _e( 'Pingback:' ); ?> <?php comment_author_link( $comment ); ?> <?php edit_comment_link( __( 'Edit' ), '<span class="edit-link">', '</span>' ); ?>
<?php echo $label; ?> <?php comment_author_link( $comment ); ?> <?php edit_comment_link( __( 'Edit' ), '<span class="edit-link">', '</span>' ); ?>
</div>
<?php
}
Expand Down
63 changes: 60 additions & 3 deletions src/wp-includes/class-wp-comment-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ class WP_Comment_Query {
*/
public $max_num_pages = 0;

/**
* Comment types a 'pings' type token expands to.
*
* Resolved once per query in get_comments(), so that the set folded into the cache
* key is the same one get_comment_ids() builds the SQL from. Null until resolved.
*
* @since 7.1.0
* @var string[]|null
*/
protected $ping_comment_types = null;

/**
* Make private/protected methods readable for backward compatibility.
*
Expand Down Expand Up @@ -149,6 +160,7 @@ public function __call( $name, $arguments ) {
* @since 4.9.0 Introduced the `$paged` argument.
* @since 5.1.0 Introduced the `$meta_compare_key` argument.
* @since 5.3.0 Introduced the `$meta_type_key` argument.
* @since 7.1.0 A `$type` of 'pings' expands to every comment type registered with `is_ping`.
*
* @param string|array $query {
* Optional. Array or query string of comment query parameters. Default empty.
Expand Down Expand Up @@ -251,7 +263,8 @@ public function __call( $name, $arguments ) {
* 'approve' (`comment_status=1`), 'all', or a custom
* comment status. Default 'all'.
* @type string|string[] $type Include comments of a given type, or array of types.
* Accepts 'comment', 'pings' (includes 'pingback' and
* Accepts 'comment', 'pings' (every comment type registered
* with `is_ping`, which includes 'pingback' and
* 'trackback'), or any custom type string. Default empty.
* @type string[] $type__in Include comments from a given array of comment types.
* Default empty.
Expand Down Expand Up @@ -455,6 +468,21 @@ public function get_comments() {
$_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) );
unset( $_args['fields'], $_args['update_comment_meta_cache'], $_args['update_comment_post_cache'] );

/*
* A 'pings' token expands to the registered ping types, which a plugin can change
* from one request to the next, so the resolved set belongs in the cache key. The
* comment last_changed salt only moves when a comment does, and would not catch it.
*/
$type_query_vars = array_merge(
(array) $this->query_vars['type'],
(array) $this->query_vars['type__in'],
(array) $this->query_vars['type__not_in']
);

if ( in_array( 'pings', $type_query_vars, true ) ) {
$_args['ping_comment_types'] = $this->get_ping_comment_types();
}

$key = md5( serialize( $_args ) );
$last_changed = wp_cache_get_last_changed( 'comment' );

Expand Down Expand Up @@ -806,8 +834,9 @@ protected function get_comment_ids() {
break;

case 'pings':
$comment_types[ $operator ][] = "'pingback'";
$comment_types[ $operator ][] = "'trackback'";
foreach ( $this->get_ping_comment_types() as $ping_type ) {
$comment_types[ $operator ][] = $wpdb->prepare( '%s', $ping_type );
}
break;

default:
Expand Down Expand Up @@ -1006,6 +1035,34 @@ protected function get_comment_ids() {
}
}

/**
* Resolves the comment types a 'pings' type token expands to.
*
* Matches how separate_comments() and wp_list_comments() group pings, so that a
* query for 'pings' returns the comments a theme would list under that heading.
*
* @since 7.1.0
*
* @return string[] Comment type names.
*/
protected function get_ping_comment_types() {
if ( null === $this->ping_comment_types ) {
$ping_types = get_comment_types( array( 'is_ping' => true ), 'names' );

/*
* The built-in ping types, for the window before comment types are registered
* on 'init' and for any install running without the registry.
*/
if ( ! $ping_types ) {
$ping_types = array( 'pingback', 'trackback' );
}

$this->ping_comment_types = array_values( $ping_types );
}

return $this->ping_comment_types;
}

/**
* Populates found_comments and max_num_pages properties for the current
* query if the limit clause was used.
Expand Down
25 changes: 25 additions & 0 deletions src/wp-includes/class-wp-comment-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,30 @@ final class WP_Comment_Type {
*/
public $render_callback = null;

/**
* Whether the comment type represents a ping (a notification from another site)
* rather than a human-authored comment.
*
* Ping types (such as `pingback` and `trackback`) are grouped together by
* {@see separate_comments()} and returned by a `'pings'` type query in
* {@see WP_Comment_Query}. When the `short_ping` argument of wp_list_comments()
* is true, they are rendered with the compact ping markup by
* {@see Walker_Comment}, which labels the comment with the type's
* `singular_name`, so a ping type should register one. A registered
* `render_callback` takes precedence over the ping markup. Like
* `render_callback`, the rendering effects apply only to classic themes; block
* themes do not use Walker_Comment.
*
* The flag drives grouping and display only. It does not change how a comment of
* this type is validated, moderated, or notified about: the pingback and trackback
* paths in {@see check_comment()}, {@see get_default_comment_status()}, and the
* notification emails are still keyed to those two type names. Default false.
*
* @since 7.1.0
* @var bool
*/
public $is_ping = false;

/**
* Whether the comment type is hierarchical.
*
Expand Down Expand Up @@ -230,6 +254,7 @@ public function set_props( $args ) {
'public' => true,
'internal' => false,
'render_callback' => null,
'is_ping' => false,
'_builtin' => false,
);

Expand Down
6 changes: 4 additions & 2 deletions src/wp-includes/comment-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -2230,8 +2230,10 @@ function _get_comment_reply_id( $post = null ) {
* 'div' will result in no additional list markup. Default 'ul'.
* @type callable $callback Callback function to use. Default null.
* @type callable $end-callback Callback function to use at the end. Default null.
* @type string $type Type of comments to list. Accepts 'all', 'comment',
* 'pingback', 'trackback', 'pings'. Default 'all'.
* @type string $type Type of comments to list. Accepts 'all', any comment type
* slug, or 'pings' (the comments of every registered comment
* type with the 'is_ping' property, which includes pingbacks
* and trackbacks). Default 'all'.
* @type int $page Page ID to list comments for. Default empty.
* @type int $per_page Number of comments to list per page. Default empty.
* @type int $avatar_size Height and width dimensions of the avatar size. Default 32.
Expand Down
20 changes: 19 additions & 1 deletion src/wp-includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ function create_initial_comment_types() {
'singular_name' => __( 'Pingback' ),
),
'public' => true,
'is_ping' => true,
'_builtin' => true,
)
);
Expand All @@ -377,6 +378,7 @@ function create_initial_comment_types() {
'singular_name' => __( 'Trackback' ),
),
'public' => true,
'is_ping' => true,
'_builtin' => true,
)
);
Expand Down Expand Up @@ -449,6 +451,17 @@ function create_initial_comment_types() {
* default, through the
* {@see 'default_excluded_comment_types'} filter.
* Default false.
* @type bool $is_ping Whether the comment type represents a ping (a notification
* from another site) rather than a human-authored comment.
* Ping types are grouped together by separate_comments(),
* returned by a 'pings' type query, and, when
* wp_list_comments() is called with 'short_ping', rendered
* with compact ping markup by Walker_Comment, labeled with the
* type's singular name. A registered 'render_callback' takes
* precedence over the ping markup. The flag drives grouping
* and display only; validation, moderation, and notification
* still key on the 'pingback' and 'trackback' type names.
* Default false.
* @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
Expand Down Expand Up @@ -1380,6 +1393,9 @@ function wp_check_comment_flood( $is_flood, $ip, $email, $date, $avoid_die = fal
* Separates an array of comments into an array keyed by comment_type.
*
* @since 2.7.0
* @since 7.1.0 The 'pings' group contains the comments of every registered
* comment type with the `is_ping` property, rather than only
* pingbacks and trackbacks.
*
* @param WP_Comment[] $comments Array of comments.
* @return array<string, WP_Comment[]> Array of comments keyed by comment type.
Expand All @@ -1403,7 +1419,9 @@ function separate_comments( &$comments ) {

$comments_by_type[ $type ][] = &$comments[ $i ];

if ( 'trackback' === $type || 'pingback' === $type ) {
$comment_type_object = get_comment_type_object( $type );

if ( $comment_type_object && $comment_type_object->is_ping ) {
$comments_by_type['pings'][] = &$comments[ $i ];
}
}
Expand Down
84 changes: 84 additions & 0 deletions tests/phpunit/tests/comment/query.php
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,90 @@ public function test_query_type_pings() {
$this->assertSameSets( array( $c2, $c3 ), $found );
}

/**
* A 'pings' query returns every registered ping type, matching what
* separate_comments() groups under the same name.
*
* @ticket 35214
*
* @covers WP_Comment_Query::query
*/
public function test_query_type_pings_includes_registered_ping_types() {
register_comment_type( 'webmention', array( 'is_ping' => true ) );

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

$q = new WP_Comment_Query();
$found = $q->query(
array(
'type' => 'pings',
'fields' => 'ids',
)
);

$this->assertSameSets( array( $pingback, $mention ), $found );
}

/**
* The registered ping types are part of the 'pings' cache key. Otherwise a plugin
* registering a ping type would keep serving results cached before it existed, since
* the comment last_changed salt only moves when a comment does.
*
* @ticket 35214
*
* @covers WP_Comment_Query::get_comments
*/
public function test_query_type_pings_is_not_served_from_a_stale_cache() {
$pingback = self::factory()->comment->create(
array(
'comment_post_ID' => self::$post_id,
'comment_approved' => '1',
'comment_type' => 'pingback',
)
);
$mention = self::factory()->comment->create(
array(
'comment_post_ID' => self::$post_id,
'comment_approved' => '1',
'comment_type' => 'webmention',
)
);

$args = array(
'type' => 'pings',
'fields' => 'ids',
);

$before = ( new WP_Comment_Query() )->query( $args );

register_comment_type( 'webmention', array( 'is_ping' => true ) );

$after = ( new WP_Comment_Query() )->query( $args );

$this->assertSameSets( array( $pingback ), $before, 'Before registration the type is not a ping.' );
$this->assertSameSets( array( $pingback, $mention ), $after, 'After registration it is.' );
}

/**
* Comments and custom
*
Expand Down
Loading
Loading