Skip to content
Merged
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 readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ Please make sure you make a backup of your database before updating any version
== Changelog ==

= 2.6.4 =
* Improved: the proxy now also forwards the visitor's Accept-Language and User-Agent Client Hints (Sec-CH-UA*) headers to Plausible Analytics, so its bot detection works for proxied requests, too.
* Fixed: on sites using WPML (or another multilingual plugin) with a directory per language, the Domain Name could be pre-filled with the language directory included (e.g. example.com/fr), which made the Plugin Token show as invalid.
* Fixed: for user roles that aren't tracked (administrators by default), some integrations still loaded the tracker without initializing it, which caused a "window.plausible is not a function" error, e.g. on search results, 404 and checkout pages. They no longer load anything for those roles.
* Fixed: events sent from the server (WooCommerce and Easy Digital Downloads add to cart, remove from cart and EDD checkout, and Contact Form 7 and Gravity Forms submissions) were also sent for user roles that aren't tracked.
Expand Down
43 changes: 39 additions & 4 deletions src/Proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,50 @@ public function send_event( $request ) {
$url,
[
'user-agent' => $ua,
'headers' => [
'X-Forwarded-For' => $ip,
'Content-Type' => 'application/json',
],
'headers' => array_merge(
$this->get_forwarded_headers(),
[
'X-Forwarded-For' => $ip,
'Content-Type' => 'application/json',
]
),
'body' => wp_kses_no_null( $params ),
]
);
}

/**
* Returns the visitor's Accept-Language and User-Agent Client Hints (Sec-CH-UA*) headers, so Plausible's bot
* detection can use them for proxied requests, too.
*
* @since v2.6.4
*
* @return array
*/
public function get_forwarded_headers() {
$headers = [];

foreach ( $_SERVER as $key => $value ) {
if ( $key !== 'HTTP_ACCEPT_LANGUAGE' && strpos( $key, 'HTTP_SEC_CH_UA' ) !== 0 ) {
continue;
}

$value = trim( wp_kses( wp_unslash( (string) $value ), 'strip' ) );

if ( $value === '' ) {
continue;
}

// e.g. HTTP_SEC_CH_UA_PLATFORM -> Sec-CH-UA-Platform
$name = str_replace( ' ', '-', ucwords( strtolower( str_replace( '_', ' ', substr( $key, 5 ) ) ) ) );
$name = str_replace( 'Sec-Ch-Ua', 'Sec-CH-UA', $name );

$headers[ $name ] = $value;
}

return $headers;
}

/**
* @return string
*
Expand Down
68 changes: 68 additions & 0 deletions tests/integration/ProxyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,72 @@ public function testGenerateEventUrl() {

$this->assertEquals( 'http://example.org/test', $url );
}

/**
* @see Proxy::get_forwarded_headers()
* @return void
*/
public function testGetForwardedHeaders() {
$proxy = new Proxy( false );

$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'nl-NL,nl;q=0.9,en;q=0.8';
$_SERVER['HTTP_SEC_CH_UA'] = '"Chromium";v="128", "Not;A=Brand";v="24"';
$_SERVER['HTTP_SEC_CH_UA_MOBILE'] = '?0';
$_SERVER['HTTP_SEC_CH_UA_PLATFORM'] = '"Windows"';
$_SERVER['HTTP_SEC_CH_UA_FULL_VERSION_LIST'] = '';
$_SERVER['HTTP_SEC_FETCH_SITE'] = 'same-origin';

try {
$this->assertEquals(
[
'Accept-Language' => 'nl-NL,nl;q=0.9,en;q=0.8',
'Sec-CH-UA' => '"Chromium";v="128", "Not;A=Brand";v="24"',
'Sec-CH-UA-Mobile' => '?0',
'Sec-CH-UA-Platform' => '"Windows"',
],
$proxy->get_forwarded_headers()
);
} finally {
unset(
$_SERVER['HTTP_ACCEPT_LANGUAGE'],
$_SERVER['HTTP_SEC_CH_UA'],
$_SERVER['HTTP_SEC_CH_UA_MOBILE'],
$_SERVER['HTTP_SEC_CH_UA_PLATFORM'],
$_SERVER['HTTP_SEC_CH_UA_FULL_VERSION_LIST'],
$_SERVER['HTTP_SEC_FETCH_SITE']
);
}
}

/**
* @see Proxy::send_event()
* @return void
*/
public function testSendEventForwardsHeaders() {
$proxy = new Proxy( false );
$sent = [];

$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'fr-FR';
$_SERVER['HTTP_SEC_CH_UA_MOBILE'] = '?1';

$capture = function ( $preempt, $args ) use ( &$sent ) {
$sent = $args['headers'];

return [ 'headers' => [], 'body' => '', 'response' => [ 'code' => 202, 'message' => 'Accepted' ], 'cookies' => [] ];
};

add_filter( 'pre_http_request', $capture, 10, 2 );

try {
$proxy->send_event( new \WP_REST_Request( 'POST' ) );

$this->assertEquals( 'fr-FR', $sent['Accept-Language'] );
$this->assertEquals( '?1', $sent['Sec-CH-UA-Mobile'] );
$this->assertEquals( 'application/json', $sent['Content-Type'] );
$this->assertArrayHasKey( 'X-Forwarded-For', $sent );
} finally {
remove_filter( 'pre_http_request', $capture );
unset( $_SERVER['HTTP_ACCEPT_LANGUAGE'], $_SERVER['HTTP_SEC_CH_UA_MOBILE'] );
}
}
}
Loading