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
81 changes: 81 additions & 0 deletions src/wp-includes/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -3163,6 +3163,87 @@ function generic_ping( $post_id = 0 ) {
return $post_id;
}

/**
* Determines whether pings should be disabled for the current environment.
*
* By default, all pings (outgoing pingbacks, trackbacks, and ping service
* notifications, as well as incoming pingbacks and trackbacks) are disabled
* for non-production environments ('local', 'development', 'staging').
*
* @since 7.1.0
*
* @return bool True if pings should be disabled, false otherwise.
*/
function wp_should_disable_pings_for_environment() {
$environment_type = wp_get_environment_type();
$should_disable = 'production' !== $environment_type;

/**
* Filters whether pings should be disabled for the current environment.
*
* Returning false re-enables pings in non-production environments.
* Returning true disables pings even in production.
*
* @since 7.1.0
*
* @param bool $should_disable Whether pings should be disabled. Default true
* for non-production environments, false for production.
* @param string $environment_type The current environment type as returned by
* wp_get_environment_type().
*/
return apply_filters( 'wp_should_disable_pings_for_environment', $should_disable, $environment_type );
}

/**
* Removes outgoing ping callbacks in non-production environments.
*
* Hooked to `do_all_pings` at priority 1 so it runs before the default
* priority 10 callbacks. Does not remove `do_all_enclosures`.
*
* @since 7.1.0
* @access private
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem private to me - all non-namespaced functions in PHP are global and public.

*/
function wp_disable_outgoing_pings_for_environment() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name assumes we'll inevitably disable pings. But it does it conditionally, based on the environment. Should we reflect that in the function name? E.g. wp_maybe_disable_outgoing_pings_for_environment.

We could also remove the _for_environment bit if that's too long.

if ( wp_should_disable_pings_for_environment() ) {
remove_action( 'do_all_pings', 'do_all_pingbacks', 10 );
remove_action( 'do_all_pings', 'do_all_trackbacks', 10 );
remove_action( 'do_all_pings', 'generic_ping', 10 );
Comment on lines +3208 to +3210
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 is the default priority, so when adding, or removing an action, you don't need to specify it.

Suggested change
remove_action( 'do_all_pings', 'do_all_pingbacks', 10 );
remove_action( 'do_all_pings', 'do_all_trackbacks', 10 );
remove_action( 'do_all_pings', 'generic_ping', 10 );
remove_action( 'do_all_pings', 'do_all_pingbacks' );
remove_action( 'do_all_pings', 'do_all_trackbacks' );
remove_action( 'do_all_pings', 'generic_ping' );

}
}

/**
* Rejects incoming trackbacks in non-production environments.
*
* Hooked to `pre_trackback_post` which fires in `wp-trackback.php` before the
* trackback is processed. Calls `trackback_response()` which sends an XML error
* response and terminates the request.
*
* @since 7.1.0
* @access private
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not really private. Let's make sure to fix that across the board in this PR.

*/
function wp_disable_trackback_for_environment() {
if ( wp_should_disable_pings_for_environment() ) {
trackback_response( 1, __( 'Trackbacks are disabled in non-production environments.' ) );
}
}

/**
* Removes the pingback XML-RPC method in non-production environments.
*
* @since 7.1.0
* @access private
*
* @param string[] $methods An array of XML-RPC methods, keyed by their methodName.
* @return string[] Modified array of XML-RPC methods.
*/
function wp_disable_xmlrpc_pingback_for_environment( $methods ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same "maybe" nit on the function naming as above.

if ( wp_should_disable_pings_for_environment() ) {
unset( $methods['pingback.ping'] );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we intentionally leaving the readonly pingback.extensions.getPingbacks() intact for non-production environments?

}

return $methods;
}

/**
* Pings back the links found in a post.
*
Expand Down
6 changes: 6 additions & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,12 @@
add_action( 'do_all_pings', 'do_all_enclosures', 10, 0 );
add_action( 'do_all_pings', 'do_all_trackbacks', 10, 0 );
add_action( 'do_all_pings', 'generic_ping', 10, 0 );

// Disable pings (pingbacks, trackbacks, and ping service notifications) in non-production environments.
add_action( 'do_all_pings', 'wp_disable_outgoing_pings_for_environment', 1, 0 );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth checking if there are any major plugins tinkering with this filter, and re-adding these callbacks before/at the default priority of 10. We can use https://wpdirectory.net/ to be sure.

add_action( 'pre_trackback_post', 'wp_disable_trackback_for_environment', 10, 0 );
add_filter( 'xmlrpc_methods', 'wp_disable_xmlrpc_pingback_for_environment' );

add_action( 'do_robots', 'do_robots' );
add_action( 'do_favicon', 'do_favicon' );
add_action( 'wp_before_include_template', 'wp_start_template_enhancement_output_buffer', 1000 ); // Late priority to let `wp_template_enhancement_output_buffer` filters and `wp_finalized_template_enhancement_output_buffer` actions be registered.
Expand Down
254 changes: 254 additions & 0 deletions tests/phpunit/tests/comment/disablePingsForEnvironment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
<?php

/**
* Tests for disabling pings in non-production environments.
*
* @group comment
* @covers ::wp_should_disable_pings_for_environment
* @covers ::wp_disable_outgoing_pings_for_environment
* @covers ::wp_disable_trackback_for_environment
* @covers ::wp_disable_xmlrpc_pingback_for_environment
*
* @ticket 64837
*/
class Tests_Comment_DisablePingsForEnvironment extends WP_UnitTestCase {

/**
* Stores the original WP_ENVIRONMENT_TYPE env value for cleanup.
*
* @var string|false
*/
private $original_env;

public function set_up() {
parent::set_up();
$this->original_env = getenv( 'WP_ENVIRONMENT_TYPE' );
}

public function tear_down() {
if ( false === $this->original_env ) {
putenv( 'WP_ENVIRONMENT_TYPE' );
} else {
putenv( 'WP_ENVIRONMENT_TYPE=' . $this->original_env );
}
parent::tear_down();
}

/**
* @ticket 64837
*/
public function test_should_disable_returns_true_for_local() {
putenv( 'WP_ENVIRONMENT_TYPE=local' );
$this->assertTrue( wp_should_disable_pings_for_environment() );
}

/**
* @ticket 64837
*/
public function test_should_disable_returns_true_for_development() {
putenv( 'WP_ENVIRONMENT_TYPE=development' );
$this->assertTrue( wp_should_disable_pings_for_environment() );
}

/**
* @ticket 64837
*/
public function test_should_disable_returns_true_for_staging() {
putenv( 'WP_ENVIRONMENT_TYPE=staging' );
$this->assertTrue( wp_should_disable_pings_for_environment() );
}

/**
* @ticket 64837
*/
public function test_should_disable_returns_false_for_production() {
putenv( 'WP_ENVIRONMENT_TYPE=production' );
$this->assertFalse( wp_should_disable_pings_for_environment() );
}

/**
* @ticket 64837
*/
public function test_filter_can_enable_pings_in_non_production() {
putenv( 'WP_ENVIRONMENT_TYPE=local' );
add_filter( 'wp_should_disable_pings_for_environment', '__return_false' );

$this->assertFalse( wp_should_disable_pings_for_environment() );
}

/**
* @ticket 64837
*/
public function test_filter_can_disable_pings_in_production() {
putenv( 'WP_ENVIRONMENT_TYPE=production' );
add_filter( 'wp_should_disable_pings_for_environment', '__return_true' );

$this->assertTrue( wp_should_disable_pings_for_environment() );
}

/**
* @ticket 64837
*/
public function test_filter_receives_environment_type() {
putenv( 'WP_ENVIRONMENT_TYPE=staging' );

$received_type = null;
add_filter(
'wp_should_disable_pings_for_environment',
function ( $should_disable, $environment_type ) use ( &$received_type ) {
$received_type = $environment_type;
return $should_disable;
},
10,
2
);

wp_should_disable_pings_for_environment();

$this->assertSame( 'staging', $received_type );
}

/**
* @ticket 64837
*/
public function test_outgoing_pingbacks_removed_in_non_production() {
putenv( 'WP_ENVIRONMENT_TYPE=development' );

// Re-register the defaults to ensure a clean state.
add_action( 'do_all_pings', 'do_all_pingbacks', 10, 0 );

// Fire the priority-1 callback.
wp_disable_outgoing_pings_for_environment();

$this->assertFalse( has_action( 'do_all_pings', 'do_all_pingbacks' ) );
}

/**
* @ticket 64837
*/
public function test_outgoing_trackbacks_removed_in_non_production() {
putenv( 'WP_ENVIRONMENT_TYPE=development' );

add_action( 'do_all_pings', 'do_all_trackbacks', 10, 0 );

wp_disable_outgoing_pings_for_environment();

$this->assertFalse( has_action( 'do_all_pings', 'do_all_trackbacks' ) );
}

/**
* @ticket 64837
*/
public function test_outgoing_generic_ping_removed_in_non_production() {
putenv( 'WP_ENVIRONMENT_TYPE=development' );

add_action( 'do_all_pings', 'generic_ping', 10, 0 );

wp_disable_outgoing_pings_for_environment();

$this->assertFalse( has_action( 'do_all_pings', 'generic_ping' ) );
}

/**
* @ticket 64837
*/
public function test_enclosures_not_removed_in_non_production() {
putenv( 'WP_ENVIRONMENT_TYPE=development' );

add_action( 'do_all_pings', 'do_all_enclosures', 10, 0 );

wp_disable_outgoing_pings_for_environment();

$this->assertSame( 10, has_action( 'do_all_pings', 'do_all_enclosures' ) );
}

/**
* @ticket 64837
*/
public function test_outgoing_pings_preserved_in_production() {
putenv( 'WP_ENVIRONMENT_TYPE=production' );

add_action( 'do_all_pings', 'do_all_pingbacks', 10, 0 );
add_action( 'do_all_pings', 'do_all_trackbacks', 10, 0 );
add_action( 'do_all_pings', 'generic_ping', 10, 0 );

wp_disable_outgoing_pings_for_environment();

$this->assertSame( 10, has_action( 'do_all_pings', 'do_all_pingbacks' ), 'do_all_pingbacks should still be hooked.' );
$this->assertSame( 10, has_action( 'do_all_pings', 'do_all_trackbacks' ), 'do_all_trackbacks should still be hooked.' );
$this->assertSame( 10, has_action( 'do_all_pings', 'generic_ping' ), 'generic_ping should still be hooked.' );
}

/**
* @ticket 64837
*/
public function test_trackback_hook_is_registered() {
$this->assertSame( 10, has_action( 'pre_trackback_post', 'wp_disable_trackback_for_environment' ) );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above about how we test the priority.

}

/**
* @ticket 64837
*/
public function test_pings_open_unaffected_by_environment() {
putenv( 'WP_ENVIRONMENT_TYPE=local' );

$post = self::factory()->post->create_and_get(
array( 'ping_status' => 'open' )
);

$this->assertTrue( pings_open( $post ) );
}

/**
* @ticket 64837
*/
public function test_xmlrpc_pingback_removed_in_non_production() {
putenv( 'WP_ENVIRONMENT_TYPE=development' );

$methods = array(
'pingback.ping' => 'this:pingback_ping',
'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
'wp.getUsersBlogs' => 'this:wp_getUsersBlogs',
);

$filtered = wp_disable_xmlrpc_pingback_for_environment( $methods );

$this->assertArrayNotHasKey( 'pingback.ping', $filtered );
}

/**
* @ticket 64837
*/
public function test_xmlrpc_pingback_preserved_in_production() {
putenv( 'WP_ENVIRONMENT_TYPE=production' );

$methods = array(
'pingback.ping' => 'this:pingback_ping',
'wp.getUsersBlogs' => 'this:wp_getUsersBlogs',
);

$filtered = wp_disable_xmlrpc_pingback_for_environment( $methods );

$this->assertArrayHasKey( 'pingback.ping', $filtered );
}

/**
* @ticket 64837
*/
public function test_xmlrpc_other_methods_preserved_in_non_production() {
putenv( 'WP_ENVIRONMENT_TYPE=development' );

$methods = array(
'pingback.ping' => 'this:pingback_ping',
'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
'wp.getUsersBlogs' => 'this:wp_getUsersBlogs',
'wp.getPost' => 'this:wp_getPost',
);

$filtered = wp_disable_xmlrpc_pingback_for_environment( $methods );

$this->assertArrayHasKey( 'pingback.extensions.getPingbacks', $filtered );
$this->assertArrayHasKey( 'wp.getUsersBlogs', $filtered );
$this->assertArrayHasKey( 'wp.getPost', $filtered );
}
}
Loading