Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added: Woocommerce integration when Ecommerce Revenue tracking is enabled. #214

Merged
merged 32 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
45dc157
Added: track WooCommerce purchases on thank you page.
Dan0sz May 23, 2024
40dd735
Fixed: make sure purchases aren't tracked twice.
Dan0sz May 23, 2024
4839f36
Fixed: track Add to cart and Remove cart item events.
Dan0sz May 23, 2024
e78ff71
Improved: assure our HTTP response code is returned when using the Pr…
Dan0sz May 24, 2024
23ff680
Ignore safety check.
Dan0sz May 24, 2024
d17d982
Added: track entered checkout event.
Dan0sz May 24, 2024
d9a6151
Remove Babel, since we no longer need it.
Dan0sz May 28, 2024
d16c141
Fixed: assure that the _wp_http_referer header is always available wh…
Dan0sz May 28, 2024
9a85fad
Fixed: re-factored Custom Properties to be sent, so they're properly …
Dan0sz May 29, 2024
93444fd
Fixed: re-factored add_to_cart_on_product_page so quantity and other …
Dan0sz May 30, 2024
c97104d
Fixed: is_api_token_valid() would return true when in some situations…
Dan0sz May 31, 2024
6f3039b
Fixed: make sure get_domain() returns current options are used if cur…
Dan0sz May 31, 2024
9114820
Improved: logic of previous fix should be moved to get_settings()
Dan0sz May 31, 2024
04fb33a
Fixed: track AJAX (i.e. non-interactivity API) add to cart events for…
Dan0sz Jun 1, 2024
7a60dfa
Fixed: ID and Product Name weren't properly tracked on product overvi…
Dan0sz Jun 1, 2024
193445f
Improved: only store valid API tokens in DB.
Dan0sz Jun 4, 2024
ed6e0ba
Removed order_id and customer_id from custom properties, due to GDPR …
Dan0sz Jun 5, 2024
c232c1a
PHPDoc.
Dan0sz Jun 5, 2024
06c4e65
Removed transaction_id as well.
Dan0sz Jun 5, 2024
3b77e51
Ignore safety measures and untestable code.
Dan0sz Jun 6, 2024
25faee6
This code won't be tested here.
Dan0sz Jun 6, 2024
6441a35
There's no reason to assume this would fail.
Dan0sz Jun 7, 2024
845b9ae
Added: plausible_analytics_integrations_* filters.
Dan0sz Jun 7, 2024
0890bdd
Added: testGetPostSettings and expanded testGetFilename
Dan0sz Jun 7, 2024
3b493b8
Prevent duplicate test values.
Dan0sz Jun 7, 2024
4bea534
Added: testMaybeCreateWooCommerceGoals()
Dan0sz Jun 7, 2024
e998df2
Added: WooComerceTest
Dan0sz Jun 7, 2024
bf9a776
PHPDoc.
Dan0sz Jun 7, 2024
913ffa8
PHPDoc.
Dan0sz Jun 7, 2024
0197831
Ignore AddJS
Dan0sz Jun 7, 2024
698b592
No need to modify source code.
Dan0sz Jun 7, 2024
98f360f
Minor re-factor for readability.
Dan0sz Jun 7, 2024
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
28 changes: 28 additions & 0 deletions assets/src/js/compatibility/woocommerce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Plausible Analytics
*
* WooCommerce compatibility JS.
*/
const {fetch: originalFetch} = window;

window.fetch = (...args) => {
let [resource, config] = args;

if (config.body === undefined) {
return originalFetch(resource, config);
}

let data = JSON.parse(config.body);

data.requests.forEach(function (request) {
if (!request.path.includes('cart/add-item')) {
return;
}

request.body._wp_http_referer = window.location.href;
});

config.body = JSON.stringify(data);

return originalFetch(resource, config);
};
29 changes: 4 additions & 25 deletions src/Admin/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use Exception;
use Plausible\Analytics\WP\Helpers;
use Plausible\Analytics\WP\Proxy;

class Module {
/**
Expand Down Expand Up @@ -252,31 +253,9 @@ private function test_proxy( $run = true ) {
return false; // @codeCoverageIgnore
}

$namespace = Helpers::get_proxy_resource( 'namespace' );
$base = Helpers::get_proxy_resource( 'base' );
$endpoint = Helpers::get_proxy_resource( 'endpoint' );
$request = new \WP_REST_Request( 'POST', "/$namespace/v1/$base/$endpoint" );
$request->set_body(
wp_json_encode(
[
'd' => 'plausible.test',
'n' => 'pageview',
'u' => 'https://plausible.test/test',
]
)
);

/** @var \WP_REST_Response $result */
try {
$result = rest_do_request( $request );
} catch ( \Exception $e ) { // @codeCoverageIgnore
/**
* There's no need to handle the error, because we don't want to display it anyway.
* We'll leave the parameter for backwards compatibility.
*/
return false; // @codeCoverageIgnore
}
$proxy = new Proxy( false );
$result = $proxy->do_request( 'pageview', 'plausible.test', 'https://plausible.test/test' );

return wp_remote_retrieve_response_code( $result->get_data() ) === 202;
return wp_remote_retrieve_response_code( $result ) === 202;
}
}
113 changes: 94 additions & 19 deletions src/Admin/Provisioning.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

use Plausible\Analytics\WP\Client;
use Plausible\Analytics\WP\Client\ApiException;
use Plausible\Analytics\WP\Client\Model\GoalCreateRequestCustomEvent;
use Plausible\Analytics\WP\Helpers;
use Plausible\Analytics\WP\Integrations;
use Plausible\Analytics\WP\Integrations\WooCommerce;

class Provisioning {
/**
Expand All @@ -21,11 +25,7 @@ class Provisioning {
/**
* @var string[] $custom_event_goals
*/
private $custom_event_goals = [
'404' => '404',
'outbound-links' => 'Outbound Link: Click',
'file-downloads' => 'File Download',
];
private $custom_event_goals = [];

/**
* @var string[] $custom_pageview_properties
Expand Down Expand Up @@ -59,6 +59,12 @@ public function __construct( $client = null ) {
$this->client = new Client();
}

$this->custom_event_goals = [
'404' => __( '404', 'plausible-analytics' ),
'outbound-links' => __( 'Outbound Link: Click', 'plausible-analytics' ),
'file-downloads' => __( 'File Download', 'plausible-analytics' ),
];

$this->init();
}

Expand All @@ -76,7 +82,8 @@ private function init() {
}

add_action( 'update_option_plausible_analytics_settings', [ $this, 'create_shared_link' ], 10, 2 );
add_action( 'update_option_plausible_analytics_settings', [ $this, 'create_goals' ], 10, 2 );
add_action( 'update_option_plausible_analytics_settings', [ $this, 'maybe_create_goals' ], 10, 2 );
add_action( 'update_option_plausible_analytics_settings', [ $this, 'maybe_create_woocommerce_goals' ], 10, 2 );
add_action( 'update_option_plausible_analytics_settings', [ $this, 'maybe_delete_goals' ], 11, 2 );
add_action( 'update_option_plausible_analytics_settings', [ $this, 'maybe_create_custom_properties' ], 11, 2 );
}
Expand Down Expand Up @@ -117,36 +124,62 @@ public function create_shared_link( $old_settings, $settings ) {
* @param $old_settings
* @param $settings
*/
public function create_goals( $old_settings, $settings ) {
public function maybe_create_goals( $old_settings, $settings ) {
$enhanced_measurements = array_filter( $settings[ 'enhanced_measurements' ] );

if ( empty( $enhanced_measurements ) ) {
return; // @codeCoverageIgnore
}

$custom_event_keys = array_keys( $this->custom_event_goals );
$create_request = new Client\Model\GoalCreateRequestBulkGetOrCreate();
$goals = [];

foreach ( $enhanced_measurements as $measurement ) {
if ( ! in_array( $measurement, $custom_event_keys ) ) {
continue; // @codeCoverageIgnore
}

$goals[] = new Client\Model\GoalCreateRequestCustomEvent(
[
'goal' => [
'event_name' => $this->custom_event_goals[ $measurement ],
],
'goal_type' => 'Goal.CustomEvent',
]
);
$goals[] = $this->create_request_custom_event( $this->custom_event_goals[ $measurement ] );
}

$this->create_goals( $goals );
}

/**
* @param string $name Event Name
* @param string $type CustomEvent|Revenue|Pageview
* @param string $currency Required if $type is Revenue
*
* @return GoalCreateRequestCustomEvent
*/
private function create_request_custom_event( $name, $type = 'CustomEvent', $currency = '' ) {
$props = [
'goal' => [
'event_name' => $name,
],
'goal_type' => "Goal.$type",
];

if ( $type === 'Revenue' ) {
$props[ 'goal' ][ 'currency' ] = $currency;
}

return new Client\Model\GoalCreateRequestCustomEvent( $props );
}

/**
* Create the goals using the API client and updates the IDs in the database.
*
* @param $goals
*
* @return void
*/
private function create_goals( $goals ) {
if ( empty( $goals ) ) {
return; // @codeCoverageIgnore
}

$create_request = new Client\Model\GoalCreateRequestBulkGetOrCreate();
$create_request->setGoals( $goals );
$response = $this->client->create_goals( $create_request );

Expand All @@ -165,6 +198,33 @@ public function create_goals( $old_settings, $settings ) {
}
}

/**
* @param $old_settings
* @param $settings
*
* @return void
*/
public function maybe_create_woocommerce_goals( $old_settings, $settings ) {
if ( ! Helpers::is_enhanced_measurement_enabled( 'revenue', $settings[ 'enhanced_measurements' ] ) || ! Integrations::is_wc_active() ) {
return; // @codeCoverageIgnore
}

$goals = [];
$woocommerce = new WooCommerce( false );

foreach ( $woocommerce->event_goals as $event_key => $event_goal ) {
if ( $event_key === 'purchase' ) {
$goals[] = $this->create_request_custom_event( $event_goal, 'Revenue', get_woocommerce_currency() );

continue;
}

$goals[] = $this->create_request_custom_event( $event_goal );
}

$this->create_goals( $goals );
}

/**
* Delete Custom Event Goals when an Enhanced Measurement is disabled.
*
Expand Down Expand Up @@ -206,15 +266,30 @@ public function maybe_delete_goals( $old_settings, $settings ) {
public function maybe_create_custom_properties( $old_settings, $settings ) {
$enhanced_measurements = $settings[ 'enhanced_measurements' ];

if ( ! in_array( 'pageview-props', $enhanced_measurements ) ) {
if ( ! Helpers::is_enhanced_measurement_enabled( 'pageview-props', $enhanced_measurements ) &&
! Helpers::is_enhanced_measurement_enabled( 'revenue', $enhanced_measurements ) ) {
return; // @codeCoverageIgnore
}

$create_request = new Client\Model\CustomPropEnableRequestBulkEnable();
$properties = [];

foreach ( $this->custom_pageview_properties as $property ) {
$properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] );
/**
* Enable Custom Properties for Authors & Categories option.
*/
if ( Helpers::is_enhanced_measurement_enabled( 'pageview-props', $enhanced_measurements ) ) {
foreach ( $this->custom_pageview_properties as $property ) {
$properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] );
}
}

/**
* Create Custom Properties for WooCommerce integration.
*/
if ( Helpers::is_enhanced_measurement_enabled( 'revenue', $enhanced_measurements ) && Integrations::is_wc_active() ) {
foreach ( WooCommerce::CUSTOM_PROPERTIES as $property ) {
$properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] );
}
}

$create_request->setCustomProps( $properties );
Expand Down
12 changes: 9 additions & 3 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ public function validate_api_token() {
$token = $this->api_instance->getConfig()->getPassword();
$is_valid = strpos( $token, 'plausible-plugin' ) !== false && ! empty( $features->getGoals() ) && $data_domain === Helpers::get_domain();

set_transient( 'plausible_analytics_valid_token', [ $token => $is_valid ], 86400 );
/**
* Don't cache invalid API tokens.
*/
if ( $is_valid ) {
set_transient( 'plausible_analytics_valid_token', [ $token => true ], 86400 ); // @codeCoverageIgnore
}

return $is_valid;
}
Expand All @@ -71,9 +76,10 @@ public function validate_api_token() {
* @return bool
*/
public function is_api_token_valid() {
$token = $this->api_instance->getConfig()->getPassword();
$token = $this->api_instance->getConfig()->getPassword();
$valid_tokens = get_transient( 'plausible_analytics_valid_token' );

return ! empty( get_transient( 'plausible_analytics_valid_token' )[ $token ] );
return isset( $valid_tokens[ $token ] ) && $valid_tokens[ $token ] === true;
}

/**
Expand Down
36 changes: 20 additions & 16 deletions src/Compatibility.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Plausible Analytics | Filters.
* Plausible Analytics | Compatibility.
*
* @since 1.2.5
* @package WordPress
Expand All @@ -11,6 +11,9 @@

use Exception;

/**
* @codeCoverageIgnore Because this is to be tested in a headless browser.
*/
class Compatibility {
/**
* A list of filters and actions to prevent our script from being manipulated by other plugins, known to cause issues.
Expand All @@ -24,12 +27,11 @@ public function __construct() {
add_filter( 'autoptimize_filter_js_exclude', [ $this, 'exclude_plausible_js_as_string' ] );
}

// WP Rocket
if ( defined( 'WP_ROCKET_VERSION' ) ) {
add_filter( 'rocket_excluded_inline_js_content', [ $this, 'exclude_plausible_inline_js' ] );
add_filter( 'rocket_exclude_js', [ $this, 'exclude_plausible_js' ] );
add_filter( 'rocket_minify_excluded_external_js', [ $this, 'exclude_plausible_js' ] );
add_filter( 'rocket_delay_js_scripts', [ $this, 'exclude_plausible_js' ] );
// LiteSpeed Cache
if ( defined( 'LSCWP_V' ) ) {
add_filter( 'litespeed_optimize_js_excludes', [ $this, 'exclude_plausible_js' ] );
add_filter( 'litespeed_optm_js_defer_exc', [ $this, 'exclude_plausible_inline_js' ] );
add_filter( 'litespeed_optm_gm_js_exc', [ $this, 'exclude_plausible_inline_js' ] );
}

// SG Optimizer
Expand All @@ -41,20 +43,22 @@ public function __construct() {
add_filter( 'sgo_javascript_combine_excluded_external_paths', [ $this, 'exclude_plausible_js' ] );
}

// WPML
if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
add_filter( 'rest_url', [ $this, 'wpml_compatibility' ], 10, 1 );
}

// WP Optimize
if ( defined( 'WPO_VERSION' ) ) {
add_filter( 'wp-optimize-minify-default-exclusions', [ $this, 'exclude_plausible_js' ] );
}

// LiteSpeed Cache
if ( defined( 'LSCWP_V' ) ) {
add_filter( 'litespeed_optimize_js_excludes', [ $this, 'exclude_plausible_js' ] );
add_filter( 'litespeed_optm_js_defer_exc', [ $this, 'exclude_plausible_inline_js' ] );
add_filter( 'litespeed_optm_gm_js_exc', [ $this, 'exclude_plausible_inline_js' ] );
}

if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
add_filter( 'rest_url', [ $this, 'wpml_compatibility' ], 10, 1 );
// WP Rocket
if ( defined( 'WP_ROCKET_VERSION' ) ) {
add_filter( 'rocket_excluded_inline_js_content', [ $this, 'exclude_plausible_inline_js' ] );
add_filter( 'rocket_exclude_js', [ $this, 'exclude_plausible_js' ] );
add_filter( 'rocket_minify_excluded_external_js', [ $this, 'exclude_plausible_js' ] );
add_filter( 'rocket_delay_js_scripts', [ $this, 'exclude_plausible_js' ] );
}
}

Expand Down
Loading