diff --git a/src/Integrations/WooCommerce.php b/src/Integrations/WooCommerce.php index eac2f576..962983d6 100644 --- a/src/Integrations/WooCommerce.php +++ b/src/Integrations/WooCommerce.php @@ -69,8 +69,10 @@ private function init( $init ) { */ add_action( 'woocommerce_before_add_to_cart_quantity', [ $this, 'add_cart_form_hidden_input' ] ); add_action( 'woocommerce_after_add_to_cart_form', [ $this, 'track_add_to_cart_on_product_page' ] ); - add_filter( 'woocommerce_store_api_validate_add_to_cart', [ $this, 'track_add_to_cart' ], 10, 2 ); - add_filter( 'woocommerce_ajax_added_to_cart', [ $this, 'track_ajax_add_to_cart' ] ); + add_action( 'woocommerce_store_api_validate_add_to_cart', [ $this, 'track_add_to_cart' ], 10, 2 ); + add_action( 'woocommerce_ajax_added_to_cart', [ $this, 'track_ajax_add_to_cart' ] ); + /** @see \WC_Form_Handler::add_to_cart_action() runs on priority 20. */ + add_action( 'wp_loaded', [ $this, 'track_direct_add_to_cart' ], 21 ); add_action( 'woocommerce_remove_cart_item', [ $this, 'track_remove_cart_item' ], 10, 2 ); add_action( 'wp_head', [ $this, 'track_entered_checkout' ] ); add_action( 'woocommerce_thankyou', [ $this, 'track_purchase' ] ); @@ -173,22 +175,22 @@ public function track_add_to_cart_on_product_page() { } /** - * Track (non-Interactivity API, i.e. AJAX) add to cart events. - * - * @param string|int $product_id ID of the product added to the cart. + * Track add to cart actions by direct link, e.g. ?product_type=download&add-to-cart=1&quantity=1 * * @return void * - * @codeCoverageIgnore Because we can't test XHR requests here. + * @codeCoverageIgnore Because we can't test XHR here. */ - public function track_ajax_add_to_cart( $product_id ) { - $product = wc_get_product( $product_id ); - $add_to_cart_data = [ - 'id' => $product_id, - 'quantity' => $_POST[ 'quantity' ] ?? 1, - ]; + public function track_direct_add_to_cart() { + if ( ! isset( $_REQUEST[ 'add-to-cart' ] ) || ! is_numeric( wp_unslash( $_REQUEST[ 'add-to-cart' ] ) ) ) { + return; + } - $this->track_add_to_cart( $product, $add_to_cart_data ); + $product_id = absint( wp_unslash( $_REQUEST[ 'add-to-cart' ] ) ); + $product = wc_get_product( $product_id ); + $quantity = isset( $_REQUEST[ 'quantity' ] ) ? absint( wp_unslash( $_REQUEST[ 'quantity' ] ) ) : 1; + + $this->track_add_to_cart( $product, [ 'id' => $product_id, 'quantity' => $quantity ] ); } /** @@ -241,6 +243,25 @@ private function clean_data( $product ) { return $product; } + /** + * Track (non-Interactivity API, i.e. AJAX) add to cart events. + * + * @param string|int $product_id ID of the product added to the cart. + * + * @return void + * + * @codeCoverageIgnore Because we can't test XHR requests here. + */ + public function track_ajax_add_to_cart( $product_id ) { + $product = wc_get_product( $product_id ); + $add_to_cart_data = [ + 'id' => $product_id, + 'quantity' => $_POST[ 'quantity' ] ?? 1, + ]; + + $this->track_add_to_cart( $product, $add_to_cart_data ); + } + /** * Track Remove from cart events. * diff --git a/src/Proxy.php b/src/Proxy.php index dd1c7dde..1012cbde 100644 --- a/src/Proxy.php +++ b/src/Proxy.php @@ -112,6 +112,11 @@ public function do_request( $name = 'pageview', $domain = '', $url = '', $props 'u' => $url ?: wp_get_referer(), ]; + // URL is required, so if no $url was set and no referer was found, attempt to create it from the REQUEST_URI server variable. + if ( empty( $body[ 'u' ] ) ) { + $body[ 'u' ] = $this->generate_event_url(); // @codeCoverageIgnore + } + // Revenue events use a different approach. if ( isset( $props[ 'revenue' ] ) ) { $body[ 'revenue' ] = reset( $props ); // @codeCoverageIgnore @@ -124,6 +129,23 @@ public function do_request( $name = 'pageview', $domain = '', $url = '', $props return $this->send_event( $request ); } + /** + * Attempts to generate the Event URL from available resources. + * + * @return string + */ + public function generate_event_url() { + $url = ''; + $parts = parse_url( $_SERVER[ 'REQUEST_URI' ] ); + $home_url_parts = parse_url( get_home_url() ); + + if ( isset( $home_url_parts[ 'scheme' ] ) && isset( $home_url_parts[ 'host' ] ) && isset( $parts[ 'path' ] ) ) { + $url = $home_url_parts[ 'scheme' ] . '://' . $home_url_parts [ 'host' ] . $parts[ 'path' ]; + } + + return $url; + } + /** * Formats and sends $request to the Plausible API. * diff --git a/tests/unit/ClientFactoryTest.php b/tests/integration/ClientFactoryTest.php similarity index 92% rename from tests/unit/ClientFactoryTest.php rename to tests/integration/ClientFactoryTest.php index 74486046..01413679 100644 --- a/tests/unit/ClientFactoryTest.php +++ b/tests/integration/ClientFactoryTest.php @@ -3,7 +3,7 @@ * @package Plausible Analytics Unit Tests - ClientFactory */ -namespace Plausible\Analytics\Tests\Unit; +namespace Plausible\Analytics\Tests\Integration; use Plausible\Analytics\Tests\TestCase; use Plausible\Analytics\WP\Client; diff --git a/tests/integration/ProxyTest.php b/tests/integration/ProxyTest.php new file mode 100644 index 00000000..b955368b --- /dev/null +++ b/tests/integration/ProxyTest.php @@ -0,0 +1,29 @@ +generate_event_url(); + + $this->assertEquals( 'http://example.org', $url ); + + $_SERVER[ 'REQUEST_URI' ] = '/test'; + + $url = $proxy->generate_event_url(); + + $this->assertEquals( 'http://example.org/test', $url ); + } +}