diff --git a/src/Admin/Provisioning.php b/src/Admin/Provisioning.php index 3e71a560..2ad99435 100644 --- a/src/Admin/Provisioning.php +++ b/src/Admin/Provisioning.php @@ -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 { /** @@ -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 @@ -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(); } @@ -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 ); } @@ -117,7 +124,7 @@ 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 ) ) { @@ -125,7 +132,6 @@ public function create_goals( $old_settings, $settings ) { } $custom_event_keys = array_keys( $this->custom_event_goals ); - $create_request = new Client\Model\GoalCreateRequestBulkGetOrCreate(); $goals = []; foreach ( $enhanced_measurements as $measurement ) { @@ -133,20 +139,47 @@ public function create_goals( $old_settings, $settings ) { 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 ); @@ -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; + } + + $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. * @@ -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 ); diff --git a/src/ECommerce.php b/src/ECommerce.php deleted file mode 100644 index 588a2174..00000000 --- a/src/ECommerce.php +++ /dev/null @@ -1,39 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { %s });'; - - /** - * Build class. - */ - public function __construct() { - $this->init(); - } - - /** - * Execute Ecommerce integrations. - * - * @return void - */ - private function init() { - // WooCommerce - if ( function_exists( 'WC' ) ) { - new ECommerce\WooCommerce(); - } - - // Easy Digital Downloads - if ( function_exists( 'EDD' ) ) { - // new ECommerce\EDD(); - } - } -} diff --git a/src/Helpers.php b/src/Helpers.php index a0819788..1ceb7906 100644 --- a/src/Helpers.php +++ b/src/Helpers.php @@ -52,11 +52,20 @@ public static function get_filename( $local = false ) { } foreach ( [ 'outbound-links', 'file-downloads', 'tagged-events', 'revenue', 'pageview-props', 'compat', 'hash' ] as $extension ) { - if ( is_array( $settings[ 'enhanced_measurements' ] ) && in_array( $extension, $settings[ 'enhanced_measurements' ], true ) ) { + if ( self::is_enhanced_measurement_enabled( $extension ) ) { $file_name .= '.' . $extension; } } + /** + * Custom Events needs to be enabled, if Revenue Tracking is enabled and any of the available integrations are available. + */ + if ( ! self::is_enhanced_measurement_enabled( 'tagged-events' ) && + self::is_enhanced_measurement_enabled( 'revenue' ) && + ( Integrations::is_wc_active() || Integrations::is_edd_active() ) ) { + $file_name .= '.' . 'tagged-events'; + } + // Load exclusions.js if any excluded pages are set. if ( ! empty( $settings[ 'excluded_pages' ] ) ) { $file_name .= '.' . 'exclusions'; @@ -169,6 +178,27 @@ public static function get_proxy_resources() { return $resources; } + /** + * Check if a certain Enhanced Measurement is enabled. + * + * @param string $name Name of the option to check, valid values are + * 404|outbound-links|file-downloads|tagged-events|revenue|pageview-props|hash|compat. + * @param array $enhanced_measurements Allows checking against a different set of options. + * + * @return bool + */ + public static function is_enhanced_measurement_enabled( $name, $enhanced_measurements = [] ) { + if ( empty( $enhanced_measurements ) ) { + $enhanced_measurements = Helpers::get_settings()[ 'enhanced_measurements' ]; + } + + if ( ! is_array( $enhanced_measurements ) ) { + return false; + } + + return in_array( $name, $enhanced_measurements ); + } + /** * Returns the URL of the domain where Plausible Analytics is hosted: self-hosted or cloud. * diff --git a/src/Integrations.php b/src/Integrations.php new file mode 100644 index 00000000..77173600 --- /dev/null +++ b/src/Integrations.php @@ -0,0 +1,57 @@ +document.addEventListener("DOMContentLoaded", () => { %s });'; + + /** + * Build class. + */ + public function __construct() { + $this->init(); + } + + /** + * Run available integrations. + * + * @return void + */ + private function init() { + // WooCommerce + if ( self::is_wc_active() ) { + new Integrations\WooCommerce(); + } + + // Easy Digital Downloads + if ( self::is_edd_active() ) { + // new Integrations\EDD(); + } + } + + /** + * Checks if WooCommerce is installed and activated. + * + * @return bool + */ + public static function is_wc_active() { + return function_exists( 'WC' ); + } + + /** + * Checks if Easy Digital Downloads is installed and activated. + * + * @return bool + */ + public static function is_edd_active() { + return function_exists( 'EDD' ); + } +} diff --git a/src/ECommerce/WooCommerce.php b/src/Integrations/WooCommerce.php similarity index 57% rename from src/ECommerce/WooCommerce.php rename to src/Integrations/WooCommerce.php index 2c95715a..3f970064 100644 --- a/src/ECommerce/WooCommerce.php +++ b/src/Integrations/WooCommerce.php @@ -1,15 +1,15 @@ track_add_to_cart_event_label = __( 'Add Item To Cart', 'plausible-analytics' ); - $this->track_remove_cart_item_event_label = __( 'Remove Cart Item', 'plausible-analytics' ); - $this->track_entered_checkout_event_label = __( 'Entered Checkout', 'plausible-analytics' ); - $this->track_purchase_event_label = __( 'Purchase', 'plausible-analytics' ); - - $this->init(); + public function __construct( $init = true ) { + $this->event_goals = [ + 'add-to-cart' => __( 'Add Item To Cart', 'plausible-analytics' ), + 'remove-from-cart' => __( 'Remove Cart Item', 'plausible-analytics' ), + 'checkout' => __( 'Entered Checkout', 'plausible-analytics' ), + 'purchase' => __( 'Purchase', 'plausible-analytics' ), + ]; + + $this->init( $init ); } /** @@ -73,14 +60,21 @@ public function __construct() { * * @return void */ - private function init() { + private function init( $init ) { + if ( ! $init ) { + return; + } + + /** + * Adds required JS and classes. + */ add_action( 'wp_enqueue_scripts', [ $this, 'add_js' ], 1 ); add_filter( 'woocommerce_store_api_add_to_cart_data', [ $this, 'add_http_referer' ], 10, 2 ); - add_filter( 'woocommerce_before_add_to_cart_button', [ $this, 'insert_track_form_submit_class_name' ] ); + /** - * @todo We should use woocommerce_add_to_cart action instead, but that currently doesn't trigger on consecutive adds to the cart. Fix when resolved in WC. - * @see https://wordpress.org/support/topic/woocommerce_add_to_cart-action-isnt-triggered-on-consecutive-items/ + * Trigger tracking events. */ + add_filter( '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_action( 'woocommerce_remove_cart_item', [ $this, 'track_remove_cart_item' ], 10, 2 ); add_action( 'wp_head', [ $this, 'track_entered_checkout' ] ); @@ -107,7 +101,7 @@ public function add_js() { } /** - * A bit of a hacky approach to ensuring the _wp_http_referer header is available to us when hitting the Proxy in @see self::track_add_to_cart() + * A bit of a hacky approach to ensure the _wp_http_referer header is available to us when hitting the Proxy in @see self::track_add_to_cart() * and @see self::track_remove_cart_item(). * * @param $add_to_cart_data @@ -130,16 +124,23 @@ public function add_http_referer( $add_to_cart_data, $request ) { * * @return void */ - public function insert_track_form_submit_class_name() { + public function track_add_to_cart_on_product_page() { + $product = wc_get_product(); ?> clean_data( $add_to_cart_data ); $cart = WC()->cart; $props = apply_filters( - 'plausible_analytics_ecommerce_woocommerce_track_add_to_cart_custom_properties', + 'plausible_analytics_woocommerce_add_to_cart_custom_properties', [ - 'props' => [ - 'product_name' => $product_data[ 'name' ], - 'product_id' => $added_to_cart[ 'id' ], - 'quantity' => $added_to_cart[ 'quantity' ], - 'price' => $product_data[ 'price' ], - 'tax_class' => $product_data[ 'tax_class' ], - 'cart_total_items' => count( $cart->get_cart_contents() ), - 'cart_total' => $cart->get_total(), - ], + 'product_name' => $product_data[ 'name' ], + 'product_id' => $added_to_cart[ 'id' ], + 'quantity' => $added_to_cart[ 'quantity' ], + 'price' => $product_data[ 'price' ], + 'tax_class' => $product_data[ 'tax_class' ], + 'cart_total_items' => count( $cart->get_cart_contents() ), + 'cart_total' => $cart->get_total( null ), ] ); $proxy = new Proxy( false ); - $proxy->do_request( $this->track_add_to_cart_event_label, null, null, $props ); + $proxy->do_request( $this->event_goals[ 'add-to-cart' ], null, null, $props ); } /** @@ -202,21 +201,19 @@ public function track_remove_cart_item( $cart_item_key, $cart ) { $cart_contents = $cart->get_cart_contents(); $item_removed_from_cart = $this->clean_data( $cart_contents[ $cart_item_key ] ?? [] ); $props = apply_filters( - 'plausible_analytics_ecommerce_woocommerce_track_remove_cart_item_custom_properties', + 'plausible_analytics_woocommerce_remove_cart_item_custom_properties', [ - 'props' => [ - 'product_id' => $item_removed_from_cart[ 'product_id' ], - 'variation_id' => $item_removed_from_cart[ 'variation_id' ], - 'quantity' => $item_removed_from_cart[ 'quantity' ], - 'removed_item' => $item_removed_from_cart, - 'cart_total_items' => count( $cart_contents ), - 'cart_total' => $cart->get_total(), - ], + 'product_id' => $item_removed_from_cart[ 'product_id' ], + 'variation_id' => $item_removed_from_cart[ 'variation_id' ], + 'quantity' => $item_removed_from_cart[ 'quantity' ], + 'removed_item' => $item_removed_from_cart, + 'cart_total_items' => count( $cart_contents ), + 'cart_total' => $cart->get_total( null ), ] ); $proxy = new Proxy( false ); - $proxy->do_request( $this->track_remove_cart_item_event_label, null, null, $props ); + $proxy->do_request( $this->event_goals[ 'remove-from-cart' ], null, null, $props ); } /** @@ -230,18 +227,21 @@ public function track_entered_checkout() { $session = WC()->session; $cart = WC()->cart; $props = apply_filters( - 'plausible_analytics_ecommerce_woocommerce_track_entered_checkout_custom_properties', + 'plausible_analytics_woocommerce_entered_checkout_custom_properties', [ - 'customer_id' => $session->get_customer_id(), - 'subtotal' => $cart->get_subtotal(), - 'shipping' => $cart->get_shipping_total(), - 'tax' => $cart->get_total_tax(), - 'total' => $cart->get_total(), + 'props' => [ + 'customer_id' => $session->get_customer_id(), + 'subtotal' => $cart->get_subtotal(), + 'shipping' => $cart->get_shipping_total(), + 'tax' => $cart->get_total_tax(), + 'total' => $cart->get_total( null ), + ], ] ); $props = wp_json_encode( $props ); + $label = $this->event_goals[ 'checkout' ]; - echo sprintf( ECommerce::SCRIPT_WRAPPER, "window.plausible( '$this->track_entered_checkout_event_label', $props )" ); + echo sprintf( Integrations::SCRIPT_WRAPPER, "window.plausible( '$label', $props )" ); } /** @@ -260,7 +260,7 @@ public function track_purchase( $order_id ) { } $props = apply_filters( - 'plausible_analytics_ecommerce_woocommerce_track_purchase_custom_properties', + 'plausible_analytics_woocommerce_purchase_custom_properties', [ 'transaction_id' => $order->get_transaction_id(), 'order_id' => $order_id, @@ -273,8 +273,9 @@ public function track_purchase( $order_id ) { 'props' => $props, ] ); + $label = $this->event_goals[ 'purchase' ]; - echo sprintf( ECommerce::SCRIPT_WRAPPER, "window.plausible( '$this->track_purchase_event_label', $props )" ); + echo sprintf( Integrations::SCRIPT_WRAPPER, "window.plausible( '$label', $props )" ); $order->add_meta_data( self::PURCHASE_TRACKED_META_KEY, true ); $order->save(); diff --git a/src/Plugin.php b/src/Plugin.php index cc59b4d4..e86fdfb8 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -40,10 +40,13 @@ public function register_services() { new Admin\Provisioning(); } + if ( Helpers::is_enhanced_measurement_enabled( 'revenue' ) ) { + new Integrations(); + } + new Actions(); new Ajax(); new Compatibility(); - new ECommerce(); new Filters(); new Proxy(); new Setup(); diff --git a/tests/integration/Admin/ProvisioningTest.php b/tests/integration/Admin/ProvisioningTest.php index 1b76f002..02f226a2 100644 --- a/tests/integration/Admin/ProvisioningTest.php +++ b/tests/integration/Admin/ProvisioningTest.php @@ -45,7 +45,7 @@ public function testCreateSharedLink() { } /** - * @see Provisioning::create_goals() + * @see Provisioning::maybe_create_goals() * @throws ApiException */ public function testCreateGoals() { @@ -84,7 +84,7 @@ public function testCreateGoals() { $class = new Provisioning( $mock ); - $class->create_goals( [], $settings ); + $class->maybe_create_goals( [], $settings ); $goal_ids = get_option( 'plausible_analytics_enhanced_measurements_goal_ids' );