Skip to content
Draft
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
1 change: 1 addition & 0 deletions src/wp-admin/includes/schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ function populate_options( array $options = array() ) {
'default_comment_status' => 'open',
'default_ping_status' => 'open',
'default_pingback_flag' => 1,
'auto_approve_self_pingbacks' => 0,
'posts_per_page' => 10,
/* translators: Default date format, see https://www.php.net/manual/datetime.format.php */
'date_format' => __( 'F j, Y' ),
Expand Down
2 changes: 2 additions & 0 deletions src/wp-admin/options-discussion.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
<?php _e( 'Comment must be manually approved' ); ?> </label>
<br />
<label for="comment_previously_approved"><input type="checkbox" name="comment_previously_approved" id="comment_previously_approved" value="1" <?php checked( '1', get_option( 'comment_previously_approved' ) ); ?> /> <?php _e( 'Comment author must have a previously approved comment' ); ?></label>
<br />
<label for="auto_approve_self_pingbacks"><input type="checkbox" name="auto_approve_self_pingbacks" id="auto_approve_self_pingbacks" value="1" <?php checked( '1', get_option( 'auto_approve_self_pingbacks' ) ); ?> /> <?php _e( 'Automatically approve pingbacks from this site' ); ?></label>
</fieldset></td>
</tr>
<?php $comment_moderation_title = __( 'Comment Moderation' ); ?>
Expand Down
1 change: 1 addition & 0 deletions src/wp-admin/options.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
'comment_moderation',
'require_name_email',
'comment_previously_approved',
'auto_approve_self_pingbacks',
'comment_max_links',
'moderation_keys',
'disallowed_keys',
Expand Down
47 changes: 47 additions & 0 deletions src/wp-includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,53 @@ function wp_check_comment_data( $comment_data ) {
return apply_filters( 'pre_comment_approved', $approved, $comment_data );
}

/**
* Automatically approves a pingback that originates from this site.
*
* A self-pingback is generated when one post on the site links to another post
* on the same site. When the `auto_approve_self_pingbacks` option is enabled,
* such pingbacks are approved instead of being held for moderation.
*
* Only pending pingbacks are affected; comments already flagged as approved,
* spam, or trash (or returning a WP_Error) are left untouched.
*
* The source is identified from `comment_author_url`. For a pingback this is the
* source URL that core has already fetched and verified actually links to the
* target post, so a URL resolving to a local post indicates genuine local content.
*
* @since 7.1.0
*
* @param int|string|WP_Error $approved The approval status. Accepts 1, 0, 'spam', 'trash',
* or WP_Error.
* @param array $comment_data Comment data.
* @return int|string|WP_Error The (possibly updated) approval status.
*/
function wp_auto_approve_self_pingback( $approved, $comment_data ) {
// Only act on pending comments; leave approved, spam, trash, and errors alone.
if ( 0 !== $approved && '0' !== $approved ) {
return $approved;
}

if ( ! get_option( 'auto_approve_self_pingbacks' ) ) {
return $approved;
}

if ( empty( $comment_data['comment_type'] ) || 'pingback' !== $comment_data['comment_type'] ) {
return $approved;
}

if ( empty( $comment_data['comment_author_url'] ) ) {
return $approved;
}

// The pingback source URL resolves to a post on this site, so treat it as a self-pingback.
if ( url_to_postid( $comment_data['comment_author_url'] ) > 0 ) {
return 1;
}

return $approved;
}

/**
* Checks if a comment contains disallowed characters or words.
*
Expand Down
1 change: 1 addition & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@
add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
add_filter( 'comment_flood_filter', 'wp_throttle_comment_flood', 10, 3 );
add_filter( 'pre_comment_content', 'wp_rel_ugc', 15 );
add_filter( 'pre_comment_approved', 'wp_auto_approve_self_pingback', 10, 2 );
add_filter( 'comment_email', 'antispambot' );
add_filter( 'option_tag_base', '_wp_filter_taxonomy_base' );
add_filter( 'option_category_base', '_wp_filter_taxonomy_base' );
Expand Down
11 changes: 11 additions & 0 deletions src/wp-includes/option.php
Original file line number Diff line number Diff line change
Expand Up @@ -2943,6 +2943,17 @@ function register_initial_settings() {
)
);

register_setting(
'discussion',
'auto_approve_self_pingbacks',
array(
'show_in_rest' => true,
'type' => 'boolean',
'label' => __( 'Automatically approve pingbacks from this site' ),
'description' => __( 'Automatically approve pingbacks generated by links between posts on this site.' ),
)
);

register_setting(
'discussion',
'default_comment_status',
Expand Down
122 changes: 122 additions & 0 deletions tests/phpunit/tests/comment/autoApproveSelfPingback.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

/**
* @group comment
* @group pingback
*
* @covers ::wp_auto_approve_self_pingback
*/
class Tests_Comment_AutoApproveSelfPingback extends WP_UnitTestCase {

/**
* Post that pingbacks target, used to build a self-pingback source URL.
*
* @var int
*/
public static $post_id;

public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
self::$post_id = $factory->post->create();
}

/**
* Builds comment data for a pingback whose source URL points to a local post.
*
* @param string $comment_type Comment type. Default 'pingback'.
* @return array Comment data.
*/
private function get_self_pingback_data( $comment_type = 'pingback' ) {
return array(
'comment_post_ID' => self::$post_id,
'comment_author' => 'Self Site',
'comment_author_url' => get_permalink( self::$post_id ),
'comment_content' => 'A link from this site.',
'comment_type' => $comment_type,
);
}

public function test_approves_pending_self_pingback_when_option_enabled() {
update_option( 'auto_approve_self_pingbacks', 1 );

$approved = wp_auto_approve_self_pingback( 0, $this->get_self_pingback_data() );

$this->assertSame( 1, $approved );
}

public function test_leaves_self_pingback_pending_when_option_disabled() {
update_option( 'auto_approve_self_pingbacks', 0 );

$approved = wp_auto_approve_self_pingback( 0, $this->get_self_pingback_data() );

$this->assertSame( 0, $approved );
}

public function test_ignores_non_pingback_comment_types() {
update_option( 'auto_approve_self_pingbacks', 1 );

$approved = wp_auto_approve_self_pingback( 0, $this->get_self_pingback_data( 'comment' ) );

$this->assertSame( 0, $approved );
}

public function test_does_not_approve_pingback_from_external_url() {
update_option( 'auto_approve_self_pingbacks', 1 );

$data = $this->get_self_pingback_data();
$data['comment_author_url'] = 'https://example.org/some-other-post/';

$approved = wp_auto_approve_self_pingback( 0, $data );

$this->assertSame( 0, $approved );
}

/**
* Spam, trash, approved, and error verdicts must never be downgraded or overridden.
*
* @dataProvider data_non_pending_statuses
*
* @param mixed $status A non-pending approval status.
*/
public function test_does_not_override_non_pending_status( $status ) {
update_option( 'auto_approve_self_pingbacks', 1 );

$approved = wp_auto_approve_self_pingback( $status, $this->get_self_pingback_data() );

$this->assertSame( $status, $approved );
}

public function data_non_pending_statuses() {
return array(
'already approved' => array( 1 ),
'spam' => array( 'spam' ),
'trash' => array( 'trash' ),
);
}

public function test_does_not_override_wp_error_status() {
update_option( 'auto_approve_self_pingbacks', 1 );

$error = new WP_Error( 'disallowed', 'Comment is not allowed.' );
$approved = wp_auto_approve_self_pingback( $error, $this->get_self_pingback_data() );

$this->assertSame( $error, $approved );
}

/**
* The callback must be wired to the `pre_comment_approved` filter by default,
* so a pending self-pingback is approved through the real filter chain.
*/
public function test_filter_is_registered_and_approves_self_pingback() {
$this->assertSame(
10,
has_filter( 'pre_comment_approved', 'wp_auto_approve_self_pingback' ),
'wp_auto_approve_self_pingback should be hooked to pre_comment_approved at priority 10.'
);

update_option( 'auto_approve_self_pingbacks', 1 );

$approved = apply_filters( 'pre_comment_approved', 0, $this->get_self_pingback_data() );

$this->assertSame( 1, $approved );
}
}
1 change: 1 addition & 0 deletions tests/phpunit/tests/rest-api/rest-settings-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public function test_get_items() {
'page_on_front',
'page_for_posts',
'default_ping_status',
'auto_approve_self_pingbacks',
'default_comment_status',
'site_icon', // Registered in wp-includes/blocks/site-logo.php
);
Expand Down
7 changes: 7 additions & 0 deletions tests/qunit/fixtures/wp-api-generated.js
Original file line number Diff line number Diff line change
Expand Up @@ -11193,6 +11193,12 @@ mockedApiResponse.Schema = {
],
"required": false
},
"auto_approve_self_pingbacks": {
"title": "Automatically approve pingbacks from this site",
"description": "Automatically approve pingbacks generated by links between posts on this site.",
"type": "boolean",
"required": false
},
"default_comment_status": {
"title": "Allow comments on new posts",
"description": "Allow people to submit comments on new posts.",
Expand Down Expand Up @@ -14671,6 +14677,7 @@ mockedApiResponse.settings = {
"page_on_front": 0,
"page_for_posts": 0,
"default_ping_status": "open",
"auto_approve_self_pingbacks": false,
"default_comment_status": "open",
"site_logo": null,
"site_icon": 0
Expand Down
Loading