diff --git a/includes/HiiveConnection.php b/includes/HiiveConnection.php index 8475ad1..b8871c7 100644 --- a/includes/HiiveConnection.php +++ b/includes/HiiveConnection.php @@ -322,6 +322,12 @@ public function notify( $events ) { */ public function hiive_request( string $path, ?array $payload = array(), ?array $args = array() ) { + /** + * @see \WP_Http::request() + * @see https://developer.wordpress.org/reference/hooks/http_headers_useragent/ + */ + add_filter( 'http_headers_useragent', array( $this, 'add_plugin_name_version_to_user_agent' ), 10, 2 ); + // If for some reason we are not connected, bail out now. // If we are not connected, the throttling logic should eventually reconnect. if ( ! self::is_connected() ) { @@ -363,6 +369,8 @@ public function hiive_request( string $path, ?array $payload = array(), ?array $ } } + remove_filter( 'http_headers_useragent', array( $this, 'add_plugin_name_version_to_user_agent' ) ); + return $request_response; } @@ -405,4 +413,24 @@ public function get_core_data() { return apply_filters( 'newfold_wp_data_module_core_data_filter', $data ); } + + /** + * Add the plugin name and version to the user agent string + * + * @param string $user_agent E.g. "WordPress/6.4.3; https://example.org". + * @param string $url E.g. "https://hiive.cloud/api/sites/v2/events". + * + * @return string E.g. "WordPress/6.4.3; bluehost/1.2.3; https://example.org". + */ + public function add_plugin_name_version_to_user_agent( string $user_agent, string $url ): string { + $container = container(); + $plugin_brand = sanitize_title( $container->plugin()->brand ); + $plugin_version = $container->plugin()->get( 'version', '0' ); + + $user_agent_parts = array_map( 'trim', explode( ';', $user_agent ) ); + + array_splice( $user_agent_parts, 1, 0, "{$plugin_brand}/{$plugin_version}" ); + + return implode( '; ', $user_agent_parts ); + } } diff --git a/tests/phpunit/includes/HiiveConnectionTest.php b/tests/phpunit/includes/HiiveConnectionTest.php index 16b8213..4c0265f 100644 --- a/tests/phpunit/includes/HiiveConnectionTest.php +++ b/tests/phpunit/includes/HiiveConnectionTest.php @@ -111,6 +111,8 @@ function ( string $constant_name ): string { public function test_hiive_request_returns_wperror_when_no_auth_token(): void { $sut = new HiiveConnection(); + WP_Mock::expectFilterAdded( 'http_headers_useragent', array( $sut, 'add_plugin_name_version_to_user_agent' ), 10, 2 ); + WP_Mock::userFunction( 'get_option' ) ->with( 'nfd_data_token' ) ->once() @@ -126,9 +128,12 @@ public function test_hiive_request_returns_wperror_when_no_auth_token(): void { * @covers ::notify */ public function test_notify_success(): void { + $sut = Mockery::mock( HiiveConnection::class )->makePartial(); $sut->expects( 'get_core_data' )->once()->andReturn( array( 'brand' => 'etc' ) ); + WP_Mock::expectFilterAdded( 'http_headers_useragent', array( $sut, 'add_plugin_name_version_to_user_agent' ), 10, 2 ); + $event = Mockery::mock( Event::class ); WP_Mock::userFunction( 'get_option' ) @@ -169,6 +174,15 @@ function ( $request_response ) { } ); + /** + * WP_Mock::expectFilterRemoved( 'http_headers_useragent', array( $sut, 'add_plugin_name_version_to_user_agent' ) ); + * + * @see https://github.com/10up/wp_mock/pull/246 + */ + WP_Mock::userFunction( 'NewfoldLabs\WP\Module\Data\remove_filter' ) + ->once() + ->with( 'http_headers_useragent', array( $sut, 'add_plugin_name_version_to_user_agent' ) ); + $sut->notify( array( $event ) ); $this->assertConditionsMet(); @@ -183,6 +197,8 @@ function ( $request_response ) { public function test_notify_bad_token(): void { $sut = Mockery::mock( HiiveConnection::class )->makePartial(); + WP_Mock::expectFilterAdded( 'http_headers_useragent', array( $sut, 'add_plugin_name_version_to_user_agent' ), 10, 2 ); + $event = Mockery::mock( Event::class ); $event->category = 'admin'; $event->key = 'plugin_search'; @@ -322,6 +338,15 @@ function ( string $constant_name ) use ( $temp_dir ) { ->with( 'nfd_data_token', 'hiive_auth_token' ) ->once()->andReturnTrue(); + /** + * WP_Mock::expectFilterRemoved( 'http_headers_useragent', array( $sut, 'add_plugin_name_version_to_user_agent' ) ); + * + * @see https://github.com/10up/wp_mock/pull/246 + */ + WP_Mock::userFunction( 'NewfoldLabs\WP\Module\Data\remove_filter' ) + ->twice() + ->with( 'http_headers_useragent', array( $sut, 'add_plugin_name_version_to_user_agent' ) ); + $sut->notify( array( $event ) ); $this->assertConditionsMet(); @@ -335,6 +360,8 @@ function ( string $constant_name ) use ( $temp_dir ) { public function test_fails_to_reconnect() { $sut = Mockery::mock( HiiveConnection::class )->makePartial(); + WP_Mock::expectFilterAdded( 'http_headers_useragent', array( $sut, 'add_plugin_name_version_to_user_agent' ), 10, 2 ); + WP_Mock::userFunction( 'get_option' ) ->with( 'nfd_data_token' ) ->twice() @@ -366,4 +393,20 @@ public function test_fails_to_reconnect() { self::assertInstanceOf( \WP_Error::class, $result ); self::assertEquals( 'This site is not connected to the hiive.', $result->get_error_message() ); } + + /** + * @covers ::add_plugin_name_version_to_user_agent + */ + public function test_add_plugin_name_version_to_user_agent(): void { + $plugin = Mockery::mock( Plugin::class ); + $plugin->brand = 'bluehost'; + $plugin->expects( 'get' )->once()->with( 'version', '0' )->andReturn( '1.2.3' ); + container()->set( 'plugin', $plugin ); + + $sut = new HiiveConnection(); + + $result = $sut->add_plugin_name_version_to_user_agent( 'WordPress/6.4.3; https://example.org', '' ); + + self::assertEquals( 'WordPress/6.4.3; bluehost/1.2.3; https://example.org', $result ); + } }