diff --git a/readme.txt b/readme.txt index 7b6cf192..7fd9b18c 100644 --- a/readme.txt +++ b/readme.txt @@ -278,6 +278,11 @@ Please make sure you make a backup of your database before updating any version == Changelog == += 2.6.4 = +* 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. +* Changed: logged-in users without a role on the site (e.g. multisite super admins visiting a subsite) are no longer tracked, as none of their roles is enabled under "Track analytics for user roles". Previously they were tracked like logged-out visitors. + = 2.6.3 = * Improved: the tracker script now loads asynchronously (like Plausible Analytics' own snippet) instead of deferred, so it can no longer hold up the rest of the page when it can't be reached. * Added: Hummingbird compatibility. Its Asset Optimization and Delay JavaScript no longer touch the plugin's scripts, which could cause a "plausible is not a function" error (e.g. on search results pages). diff --git a/src/Assets.php b/src/Assets.php index 5e1b6c5f..793c5907 100644 --- a/src/Assets.php +++ b/src/Assets.php @@ -34,7 +34,7 @@ private function init() { * @return void */ public function maybe_enqueue_cloaked_affiliate_links_assets() { - if ( Helpers::is_cloaked_affiliate_links_enabled() && Helpers::main_script_is_registered() ) { + if ( Helpers::is_cloaked_affiliate_links_enabled() && Helpers::main_script_is_initialized() ) { wp_enqueue_script( 'plausible-affiliate-links', PLAUSIBLE_ANALYTICS_PLUGIN_URL . 'assets/dist/js/plausible-affiliate-links.js', @@ -57,7 +57,7 @@ public function maybe_enqueue_cloaked_affiliate_links_assets() { public function maybe_enqueue_four_o_four_script() { $is_404 = apply_filters( 'plausible_analytics_is_404', is_404() ); - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::FOUR_O_FOUR ) && $is_404 && Helpers::main_script_is_registered() ) { + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::FOUR_O_FOUR ) && $is_404 && Helpers::main_script_is_initialized() ) { $data = wp_json_encode( [ 'props' => [ @@ -88,9 +88,8 @@ public function maybe_enqueue_four_o_four_script() { * @throws \Exception */ public function maybe_enqueue_main_script() { - $settings = Helpers::get_settings(); - $user_role = Helpers::get_user_role(); - $url = $this->get_js_url( true ); + $settings = Helpers::get_settings(); + $url = $this->get_js_url( true ); if ( ! $url ) { echo '\n"; @@ -104,9 +103,9 @@ public function maybe_enqueue_main_script() { wp_register_script( 'plausible-analytics', $url, [], null, apply_filters( 'plausible_load_js_in_footer', false ) ); /** - * Bail if tracked_user_roles is empty (which means no roles should be tracked) or if the current role should not be tracked. + * Bail if the current user's role should not be tracked. */ - if ( ( ! empty( $user_role ) && ! isset( $settings['tracked_user_roles'] ) ) || ( ! empty( $user_role ) && ! in_array( $user_role, $settings['tracked_user_roles'], true ) ) ) { + if ( ! Helpers::is_current_user_tracked() ) { return; // @codeCoverageIgnore } @@ -148,7 +147,7 @@ protected function get_js_url( bool $local = false ) { * @return void */ public function maybe_enqueue_query_params_script() { - if ( Helpers::is_query_params_enabled() && Helpers::main_script_is_registered() ) { + if ( Helpers::is_query_params_enabled() && Helpers::main_script_is_initialized() ) { $props = []; $query_params = Helpers::get_settings()['query_params'] ?? []; @@ -183,7 +182,7 @@ public function maybe_enqueue_query_params_script() { public function maybe_enqueue_search_queries_script() { $is_search = apply_filters( 'plausible_analytics_is_search', is_search() ); - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::SEARCH_QUERIES ) && $is_search && Helpers::main_script_is_registered() ) { + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::SEARCH_QUERIES ) && $is_search && Helpers::main_script_is_initialized() ) { global $wp_query; $search_source = isset( $_REQUEST['search_source'] ) ? sanitize_text_field( $_REQUEST['search_source'] ) : wp_get_referer(); diff --git a/src/Helpers.php b/src/Helpers.php index 4e8d3638..59778f99 100644 --- a/src/Helpers.php +++ b/src/Helpers.php @@ -827,13 +827,32 @@ public static function get_multilang_plugin_name() { * @return string */ public static function get_user_role() { - global $current_user; - - $user_roles = $current_user->roles; + // wp_get_current_user() makes sure the current user is determined, e.g. in REST API requests. + $user_roles = (array) wp_get_current_user()->roles; return array_shift( $user_roles ); } + /** + * Checks whether the current visitor should be tracked: logged-out visitors always are, logged-in users only when + * their role is enabled under "Track analytics for user roles". A logged-in user without a role on this site (e.g. a + * multisite super admin visiting a subsite) has no role that's enabled, so isn't tracked. + * + * @since 2.6.4 + * + * @return bool + */ + public static function is_current_user_tracked() { + if ( ! is_user_logged_in() ) { + return true; + } + + $user_role = static::get_user_role(); + $tracked_user_roles = static::get_settings()['tracked_user_roles'] ?? []; + + return ! empty( $user_role ) && in_array( $user_role, (array) $tracked_user_roles, true ); + } + /** * Wrapper to check if the Cloaked Affiliate Links option contains any values. * @@ -886,12 +905,38 @@ public static function is_query_params_enabled( $settings = [] ) { /** * Checks if the main Plausible Analytics script is registered. * + * The script is registered for every visitor, also for user roles that aren't tracked, so to decide whether to add + * tracking code, use @see self::main_script_is_initialized(). + * * @return bool */ public static function main_script_is_registered() { return wp_script_is( 'plausible-analytics', 'registered' ); } + /** + * Checks if the main Plausible Analytics script is enqueued and initialized, i.e. the current visitor is tracked. + * + * Tracking code must only be added when it is: for a user role that isn't tracked, the script is registered but not + * enqueued, and neither is its inline plausible.init() call. A script that depends on it would still pull the tracker + * onto the page, which then leaves window.plausible an object instead of a function, so any call to it throws + * "window.plausible is not a function". + * + * wp_script_is( 'plausible-analytics', 'enqueued' ) isn't enough: it's also true while any enqueued script depends on + * it, e.g. another plugin's. So check that it's in the queue itself and that @see Assets::maybe_enqueue_main_script() + * added the plausible.init() call, which it only does for visitors that are tracked. + * + * @since 2.6.4 + * + * @return bool + */ + public static function main_script_is_initialized() { + $scripts = wp_scripts(); + $after = (array) $scripts->get_data( 'plausible-analytics', 'after' ); + + return in_array( 'plausible-analytics', $scripts->queue, true ) && strpos( implode( "\n", $after ), 'plausible.init(' ) !== false; + } + /** * Translates a URL slug, e.g., WooCommerce's product base, to the given language. * diff --git a/src/Integrations/EDD.php b/src/Integrations/EDD.php index 954c4ed9..5835b810 100644 --- a/src/Integrations/EDD.php +++ b/src/Integrations/EDD.php @@ -10,6 +10,7 @@ namespace Plausible\Analytics\WP\Integrations; use Plausible\Analytics\WP\EnhancedMeasurements; +use Plausible\Analytics\WP\Helpers; use Plausible\Analytics\WP\Integrations; use Plausible\Analytics\WP\Proxy; @@ -84,6 +85,11 @@ private function init( $init ) { * @codeCoverageIgnore Because we can't test XHR requests here. */ public function track_add_to_cart( $download_id, $options, $items ) { + // Not for user roles that aren't tracked. @see Helpers::is_current_user_tracked() + if ( ! Helpers::is_current_user_tracked() ) { + return; + } + $download = new \EDD_Download( $download_id ); if ( $download->ID === 0 ) { @@ -126,6 +132,11 @@ function ( $item ) use ( $download_id ) { * @codeCoverageIgnore Because we can't test XHR requests here. */ public function track_remove_cart_item( $key ) { + // Not for user roles that aren't tracked. @see Helpers::is_current_user_tracked() + if ( ! Helpers::is_current_user_tracked() ) { + return; + } + $cart_contents = edd_get_cart_contents(); $item_removed_from_cart = $cart_contents[ $key ] ?? []; $product = null; @@ -170,6 +181,11 @@ public function track_remove_cart_item( $key ) { * @codeCoverageIgnore Because we can't test XHR requests here. */ public function track_entered_checkout() { + // Not for user roles that aren't tracked. @see Helpers::is_current_user_tracked() + if ( ! Helpers::is_current_user_tracked() ) { + return; + } + // Just to make sure we're where we're supposed to be. if ( ! edd_is_checkout() ) { return; @@ -203,6 +219,11 @@ public function track_purchase() { return; // @codeCoverageIgnore } + // Not for user roles that aren't tracked: window.plausible isn't initialized for them. @see Helpers::main_script_is_initialized() + if ( ! Helpers::main_script_is_initialized() ) { + return; + } + $session = edd_get_purchase_session(); $order = null; diff --git a/src/Integrations/FormSubmit.php b/src/Integrations/FormSubmit.php index 61b1d9fb..40d8f00a 100644 --- a/src/Integrations/FormSubmit.php +++ b/src/Integrations/FormSubmit.php @@ -50,7 +50,7 @@ private function init() { * @codeCoverageIgnore because there's nothing to test here. */ public function add_js() { - if ( ! Helpers::main_script_is_registered() ) { + if ( ! Helpers::main_script_is_initialized() ) { return; } @@ -107,6 +107,11 @@ public function maybe_track_submission( $result, $tags ) { * @codeCoverageIgnore because we can't test XHR requests here. */ private function track_submission( $uri ) { + // Not for user roles that aren't tracked. @see Helpers::is_current_user_tracked() + if ( ! Helpers::is_current_user_tracked() ) { + return; + } + $proxy = new Proxy( false ); $proxy->do_request( diff --git a/src/Integrations/WooCommerce.php b/src/Integrations/WooCommerce.php index cf46e5cc..dc5e7f52 100644 --- a/src/Integrations/WooCommerce.php +++ b/src/Integrations/WooCommerce.php @@ -11,6 +11,7 @@ use Plausible\Analytics\WP\Admin\Provisioning; use Plausible\Analytics\WP\EnhancedMeasurements; +use Plausible\Analytics\WP\Helpers; use Plausible\Analytics\WP\Integrations; use Plausible\Analytics\WP\Proxy; use WC_Cart; @@ -162,6 +163,11 @@ public function track_ajax_add_to_cart( $product_id ) { * @codeCoverageIgnore Because we can't test XHR requests here. */ public function track_add_to_cart( $product, $add_to_cart_data ) { + // Not for user roles that aren't tracked. @see Helpers::is_current_user_tracked() + if ( ! Helpers::is_current_user_tracked() ) { + return; + } + if ( ! $product ) { return; } @@ -273,6 +279,11 @@ public function track_entered_checkout() { return; // @codeCoverageIgnore } + // Not for user roles that aren't tracked: window.plausible isn't initialized for them. @see Helpers::main_script_is_initialized() + if ( ! Helpers::main_script_is_initialized() ) { + return; + } + $cart = $this->get_wc_cart(); $props = apply_filters( 'plausible_analytics_woocommerce_entered_checkout_custom_properties', @@ -301,6 +312,11 @@ public function track_entered_checkout() { * @return void */ public function track_purchase( $order_id ) { + // Not for user roles that aren't tracked: window.plausible isn't initialized for them. @see Helpers::main_script_is_initialized() + if ( ! Helpers::main_script_is_initialized() ) { + return; + } + $order = wc_get_order( $order_id ); $is_tracked = $order->get_meta( Integrations::PURCHASE_TRACKED_META_KEY ); @@ -337,6 +353,11 @@ public function track_purchase( $order_id ) { * @codeCoverageIgnore because we can't test XHR requests here. */ public function track_remove_cart_item( $cart_item_key, $cart ) { + // Not for user roles that aren't tracked. @see Helpers::is_current_user_tracked() + if ( ! Helpers::is_current_user_tracked() ) { + return; + } + $cart_contents = $cart->get_cart_contents(); $item_removed_from_cart = $this->clean_data( $cart_contents[ $cart_item_key ] ?? [] ); $product = null; diff --git a/tests/TestCase.php b/tests/TestCase.php index fc415bbd..49e2b12a 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -42,6 +42,35 @@ function ( $caps ) use ( $cap ) { ); } + /** + * Enqueue and initialize the main script, like Assets::maybe_enqueue_main_script() does for a visitor that's tracked. + * + * @return void + */ + public function enqueueMainScript() { + wp_register_script( 'plausible-analytics', 'https://plausible.test/js/pa-test.js', [], null ); + wp_enqueue_script( 'plausible-analytics' ); + wp_add_inline_script( 'plausible-analytics', 'plausible.init({});' ); + } + + /** + * Leave the main script registered but not enqueued, like Assets::maybe_enqueue_main_script() does for a user role + * that isn't tracked. + * + * @return void + */ + public function dequeueMainScript() { + wp_register_script( 'plausible-analytics', 'https://plausible.test/js/pa-test.js', [], null ); + wp_dequeue_script( 'plausible-analytics' ); + + // A script counts as enqueued while an enqueued script depends on it, e.g. one left behind by another test. + foreach ( wp_scripts()->queue as $handle ) { + if ( in_array( 'plausible-analytics', (array) ( wp_scripts()->registered[ $handle ]->deps ?? [] ), true ) ) { + wp_dequeue_script( $handle ); + } + } + } + /** * Checks an array for a (partial) match with $string. * diff --git a/tests/integration/AssetsTest.php b/tests/integration/AssetsTest.php index 32944fa5..d9e6060c 100644 --- a/tests/integration/AssetsTest.php +++ b/tests/integration/AssetsTest.php @@ -7,8 +7,20 @@ use Plausible\Analytics\Tests\TestCase; use Plausible\Analytics\WP\Assets; +use Plausible\Analytics\WP\Integrations\FormSubmit; class AssetsTest extends TestCase { + /** + * These tests act as a visitor that's tracked (logged out), whatever user a previous test left logged in. + * + * @return void + */ + protected function set_up() { + parent::set_up(); + + wp_set_current_user( 0 ); + } + /** * @see Assets::maybe_enqueue_cloaked_affiliate_links_assets() * @return void @@ -175,4 +187,62 @@ public function testEnqueueSearchQueriesScript() { remove_filter( 'plausible_analytics_is_search', '__return_true' ); } } + + /** + * For a user role that isn't tracked (administrators by default), the tracker is registered but not enqueued, and + * neither is its plausible.init() call. Scripts that depend on it mustn't pull it onto the page anyway: the tracker + * then leaves window.plausible an object, so their calls throw "window.plausible is not a function". + * + * @see Assets::maybe_enqueue_cloaked_affiliate_links_assets() + * @see FormSubmit::add_js() + * @return void + * @throws \Exception + */ + public function testIntegrationsDontLoadTheTrackerForUntrackedRoles() { + $user_id = wp_insert_user( [ 'user_login' => 'pa_untracked_' . wp_generate_password( 6, false ), 'user_pass' => wp_generate_password(), 'role' => 'administrator' ] ); + + wp_set_current_user( $user_id ); + add_filter( 'plausible_analytics_settings', [ $this, 'enableCloakedAffiliateLinks' ] ); + add_filter( 'plausible_analytics_settings', [ $this, 'enableFormCompletions' ] ); + + $assets = $this->getMockBuilder( Assets::class )->disableOriginalConstructor()->onlyMethods( [ 'get_js_url' ] )->getMock(); + $assets->method( 'get_js_url' )->willReturn( 'https://plausible.test/js/plausible.js' ); + $form_submit = $this->getMockBuilder( FormSubmit::class )->disableOriginalConstructor()->onlyMethods( [] )->getMock(); + + try { + $this->dequeueMainScript(); + + $assets->maybe_enqueue_main_script(); + + // Another plugin's script that depends on the tracker makes WordPress report the tracker as enqueued. + wp_enqueue_script( 'pa-test-other-plugin', 'https://example.org/other-plugin.js', [ 'plausible-analytics' ], null ); + + $form_submit->add_js(); + $assets->maybe_enqueue_cloaked_affiliate_links_assets(); + + $this->assertTrue( wp_script_is( 'plausible-analytics', 'registered' ) ); + $this->assertFalse( \Plausible\Analytics\WP\Helpers::main_script_is_initialized() ); + $this->assertFalse( wp_script_is( 'plausible-form-submit-integration', 'enqueued' ) ); + $this->assertFalse( wp_script_is( 'plausible-affiliate-links', 'enqueued' ) ); + + // Once administrators are tracked, everything is loaded. + add_filter( 'plausible_analytics_settings', [ $this, 'enableAdministratorTracking' ] ); + + $assets->maybe_enqueue_main_script(); + $form_submit->add_js(); + $assets->maybe_enqueue_cloaked_affiliate_links_assets(); + + $this->assertTrue( \Plausible\Analytics\WP\Helpers::main_script_is_initialized() ); + $this->assertTrue( wp_script_is( 'plausible-form-submit-integration', 'enqueued' ) ); + $this->assertTrue( wp_script_is( 'plausible-affiliate-links', 'enqueued' ) ); + } finally { + remove_filter( 'plausible_analytics_settings', [ $this, 'enableCloakedAffiliateLinks' ] ); + remove_filter( 'plausible_analytics_settings', [ $this, 'enableFormCompletions' ] ); + remove_filter( 'plausible_analytics_settings', [ $this, 'enableAdministratorTracking' ] ); + wp_dequeue_script( 'pa-test-other-plugin' ); + $this->dequeueMainScript(); + wp_set_current_user( 0 ); + wp_delete_user( $user_id ); + } + } } diff --git a/tests/integration/InitOptionsTest.php b/tests/integration/InitOptionsTest.php index e477bd3d..6413038b 100644 --- a/tests/integration/InitOptionsTest.php +++ b/tests/integration/InitOptionsTest.php @@ -172,6 +172,8 @@ public function testTrackLoggedInUsers() { $this->assertEquals( 'test', $params['customProperties']['user_logged_in'] ); } finally { remove_filter( 'plausible_analytics_settings', [ $this, 'enablePageviewProps' ] ); + // Don't leave the test user logged in for the tests that follow. + wp_set_current_user( 0 ); } } } diff --git a/tests/integration/Integrations/EDDTest.php b/tests/integration/Integrations/EDDTest.php index 7227fbc3..03270941 100644 --- a/tests/integration/Integrations/EDDTest.php +++ b/tests/integration/Integrations/EDDTest.php @@ -15,6 +15,7 @@ class EDDTest extends TestCase { * @see EDD::track_purchase() */ public function testTrackPurchase() { + $this->enqueueMainScript(); when( 'edd_is_success_page' )->justReturn( true ); when( 'edd_get_purchase_session' )->justReturn( [ 'purchase_key' => 'test-key' @@ -34,4 +35,19 @@ public function testTrackPurchase() { $this->assertStringContainsString( $class->event_goals['purchase'], $output ); $this->assertStringContainsString( '{"revenue":{"amount":10,"currency":"EUR"}}', $output ); } + + /** + * For a user role that isn't tracked, window.plausible isn't initialized, so no purchase should be sent. + * + * @see EDD::track_purchase() + * @return void + */ + public function testTrackPurchaseSkipsUntrackedRoles() { + $this->dequeueMainScript(); + when( 'edd_is_success_page' )->justReturn( true ); + + ob_start(); + ( new EDD( false ) )->track_purchase(); + $this->assertEquals( '', ob_get_clean() ); + } } diff --git a/tests/integration/Integrations/WooCommerceTest.php b/tests/integration/Integrations/WooCommerceTest.php index 85a736f9..459fbce4 100644 --- a/tests/integration/Integrations/WooCommerceTest.php +++ b/tests/integration/Integrations/WooCommerceTest.php @@ -48,6 +48,7 @@ public function testEventGoalsAreNotTranslated() { * @return void */ public function testTrackEnteredCheckout() { + $this->enqueueMainScript(); when( 'is_checkout' )->justReturn( true ); when( 'is_wc_endpoint_url' )->justReturn( false ); when( 'wc_get_permalink_structure' )->justReturn( [ 'product_base' => 'product' ] ); @@ -83,6 +84,7 @@ public function testTrackEnteredCheckout() { * @return void */ public function testTrackPurchase() { + $this->enqueueMainScript(); when( 'wc_get_permalink_structure' )->justReturn( [ 'product_base' => 'product' ] ); $class = new WooCommerce( false ); @@ -105,4 +107,41 @@ public function testTrackPurchase() { $class->track_purchase( 1 ); } + + /** + * For a user role that isn't tracked, window.plausible isn't initialized, so no purchase should be sent (and the + * order shouldn't be marked as tracked). + * + * @see WooCommerce::track_purchase() + * @return void + */ + public function testTrackPurchaseSkipsUntrackedRoles() { + $this->dequeueMainScript(); + when( 'wc_get_permalink_structure' )->justReturn( [ 'product_base' => 'product' ] ); + + $class = new WooCommerce( false ); + $mock = $this->getMockBuilder( 'WC_Order' )->setMethods( [ 'get_meta', 'add_meta_data', 'save' ] )->getMock(); + $mock->expects( $this->never() )->method( 'add_meta_data' ); + + when( 'wc_get_order' )->justReturn( $mock ); + + ob_start(); + $class->track_purchase( 1 ); + $this->assertEquals( '', ob_get_clean() ); + } + + /** + * @see WooCommerce::track_entered_checkout() + * @return void + */ + public function testTrackEnteredCheckoutSkipsUntrackedRoles() { + $this->dequeueMainScript(); + when( 'is_checkout' )->justReturn( true ); + when( 'is_wc_endpoint_url' )->justReturn( false ); + when( 'wc_get_permalink_structure' )->justReturn( [ 'product_base' => 'product' ] ); + + ob_start(); + ( new WooCommerce( false ) )->track_entered_checkout(); + $this->assertEquals( '', ob_get_clean() ); + } } diff --git a/tests/integration/UntrackedRolesTest.php b/tests/integration/UntrackedRolesTest.php new file mode 100644 index 00000000..9978462a --- /dev/null +++ b/tests/integration/UntrackedRolesTest.php @@ -0,0 +1,156 @@ +user_id = wp_insert_user( [ 'user_login' => 'pa_role_' . wp_generate_password( 6, false ), 'user_pass' => wp_generate_password(), 'role' => 'administrator' ] ); + $this->sent = []; + + add_filter( 'pre_http_request', [ $this, 'capture_event' ], 10, 3 ); + } + + /** + * @return void + */ + protected function tear_down() { + remove_filter( 'pre_http_request', [ $this, 'capture_event' ] ); + remove_filter( 'plausible_analytics_settings', [ $this, 'enableAdministratorTracking' ] ); + wp_set_current_user( 0 ); + wp_delete_user( $this->user_id ); + + parent::tear_down(); + } + + /** + * Records events instead of sending them. + * + * @param false|array $response + * @param array $args + * @param string $url + * + * @return false|array + */ + public function capture_event( $response, $args, $url ) { + if ( strpos( $url, '/api/event' ) === false ) { + return $response; + } + + $this->sent[] = json_decode( $args['body'], true )['n'] ?? ''; + + return [ 'headers' => [], 'body' => '', 'response' => [ 'code' => 202, 'message' => 'Accepted' ], 'cookies' => [], 'filename' => null ]; + } + + /** + * @see Helpers::is_current_user_tracked() + * @return void + */ + public function testIsCurrentUserTracked() { + wp_set_current_user( 0 ); + $this->assertTrue( Helpers::is_current_user_tracked() ); + + wp_set_current_user( $this->user_id ); + $this->assertFalse( Helpers::is_current_user_tracked() ); + + add_filter( 'plausible_analytics_settings', [ $this, 'enableAdministratorTracking' ] ); + $this->assertTrue( Helpers::is_current_user_tracked() ); + } + + /** + * A logged-in user without a role on this site (e.g. a multisite super admin visiting a subsite) isn't a logged-out + * visitor: no role of theirs is enabled, so they aren't tracked. + * + * @see Helpers::is_current_user_tracked() + * @return void + */ + public function testLoggedInUserWithoutRoleIsNotTracked() { + $user = new \WP_User( $this->user_id ); + $user->set_role( '' ); + + wp_set_current_user( $this->user_id ); + + $this->assertTrue( is_user_logged_in() ); + $this->assertEmpty( Helpers::get_user_role() ); + $this->assertFalse( Helpers::is_current_user_tracked() ); + } + + /** + * @see WooCommerce::track_add_to_cart() + * @see WooCommerce::track_remove_cart_item() + * @return void + */ + public function testWooCommerceCartEventsSkipUntrackedRoles() { + when( 'wc_get_permalink_structure' )->justReturn( [ 'product_base' => 'product' ] ); + wp_set_current_user( $this->user_id ); + + $class = new WooCommerce( false ); + $class->track_add_to_cart( $this->getMockBuilder( 'WC_Product' )->getMock(), [ 'id' => 1, 'quantity' => 1 ] ); + $class->track_remove_cart_item( 'key', $this->getMockBuilder( 'WC_Cart' )->getMock() ); + + $this->assertEquals( [], $this->sent ); + } + + /** + * @see EDD::track_add_to_cart() + * @see EDD::track_remove_cart_item() + * @see EDD::track_entered_checkout() + * @return void + */ + public function testEDDEventsSkipUntrackedRoles() { + wp_set_current_user( $this->user_id ); + + $class = new EDD( false ); + $class->track_add_to_cart( 1, [], [] ); + $class->track_remove_cart_item( 0 ); + $class->track_entered_checkout(); + + $this->assertEquals( [], $this->sent ); + } + + /** + * @see FormSubmit::track_gravity_forms_submission() + * @return void + */ + public function testFormSubmissionsRespectTrackedRoles() { + $class = $this->getMockBuilder( FormSubmit::class )->disableOriginalConstructor()->onlyMethods( [] )->getMock(); + $form = [ 'source_url' => home_url( '/contact/' ) ]; + + wp_set_current_user( $this->user_id ); + $class->track_gravity_forms_submission( $form ); + $this->assertEquals( [], $this->sent ); + + // Once administrators are tracked, the submission is sent. + add_filter( 'plausible_analytics_settings', [ $this, 'enableAdministratorTracking' ] ); + $class->track_gravity_forms_submission( $form ); + $this->assertEquals( [ 'WP Form Completions' ], $this->sent ); + } +}