| 1 |
<?php |
| 2 |
/** |
| 3 |
* Plausible Analytics | Integrations | EDD. |
| 4 |
* |
| 5 |
* @since 2.1.0 |
| 6 |
* @package WordPress |
| 7 |
* @subpackage Plausible Analytics |
| 8 |
*/ |
| 9 |
|
| 10 |
namespace Plausible\Analytics\WP\Integrations; |
| 11 |
|
| 12 |
use Plausible\Analytics\WP\EnhancedMeasurements; |
| 13 |
use Plausible\Analytics\WP\Integrations; |
| 14 |
use Plausible\Analytics\WP\Proxy; |
| 15 |
|
| 16 |
/** |
| 17 |
* @codeCoverageIgnore Because all we'd be testing is the (external) API. |
| 18 |
*/ |
| 19 |
class EDD { |
| 20 |
public $event_goals = []; |
| 21 |
|
| 22 |
/** |
| 23 |
* Build class. |
| 24 |
* |
| 25 |
* @param $init |
| 26 |
*/ |
| 27 |
public function __construct( $init = true ) { |
| 28 |
$uri = defined( 'EDD_SLUG' ) ? EDD_SLUG : 'downloads'; |
| 29 |
|
| 30 |
if ( is_multisite() ) { |
| 31 |
$uri = get_blog_details()->path . $uri; |
| 32 |
} else { |
| 33 |
$uri = '/' . $uri; |
| 34 |
} |
| 35 |
|
| 36 |
$this->event_goals = [ |
| 37 |
// translators: %s: Product page URI pattern. |
| 38 |
'view-product' => sprintf( __( 'Visit %s*', 'plausible-analytics' ), $uri ), |
| 39 |
'add-to-cart' => __( 'EDD Add to Cart', 'plausible-analytics' ), |
| 40 |
'remove-from-cart' => __( 'EDD Remove from Cart', 'plausible-analytics' ), |
| 41 |
'checkout' => __( 'EDD Start Checkout', 'plausible-analytics' ), |
| 42 |
'purchase' => __( 'EDD Complete Purchase', 'plausible-analytics' ), |
| 43 |
]; |
| 44 |
|
| 45 |
$this->init( $init ); |
| 46 |
} |
| 47 |
|
| 48 |
/** |
| 49 |
* Action & filter hooks. |
| 50 |
* |
| 51 |
* @param $init |
| 52 |
* |
| 53 |
* @return void |
| 54 |
*/ |
| 55 |
private function init( $init ) { |
| 56 |
if ( ! $init ) { |
| 57 |
return; |
| 58 |
} |
| 59 |
|
| 60 |
add_action( 'edd_post_add_to_cart', [ $this, 'track_add_to_cart' ], 10, 3 ); |
| 61 |
add_action( 'edd_pre_remove_from_cart', [ $this, 'track_remove_cart_item' ], 10 ); |
| 62 |
add_action( 'edd_before_purchase_form', [ $this, 'track_entered_checkout' ] ); |
| 63 |
add_action( 'wp_head', [ $this, 'track_purchase' ] ); |
| 64 |
} |
| 65 |
|
| 66 |
/** |
| 67 |
* Tracks the "add to cart" event with relevant product and cart data. |
| 68 |
* |
| 69 |
* @param int $download_id The ID of the product being added to the cart. |
| 70 |
* @param array $options Optional data associated with the product being added. |
| 71 |
* @param array $items The current items in the cart. |
| 72 |
* |
| 73 |
* @return void |
| 74 |
* |
| 75 |
* @codeCoverageIgnore Because we can't test XHR requests here. |
| 76 |
*/ |
| 77 |
public function track_add_to_cart( $download_id, $options, $items ) { |
| 78 |
$download = new \EDD_Download( $download_id ); |
| 79 |
|
| 80 |
if ( $download->ID === 0 ) { |
| 81 |
return; |
| 82 |
} |
| 83 |
|
| 84 |
$quantity = array_filter( |
| 85 |
$items, |
| 86 |
function ( $item ) use ( $download_id ) { |
| 87 |
return $item['id'] === $download_id; |
| 88 |
} |
| 89 |
); |
| 90 |
$quantity = reset( $quantity )['quantity'] ?? 1; |
| 91 |
|
| 92 |
$props = apply_filters( |
| 93 |
'plausible_analytics_edd_add_to_cart_custom_properties', |
| 94 |
[ |
| 95 |
'product_name' => edd_get_download_name( $download_id ), |
| 96 |
'product_id' => $download_id, |
| 97 |
'quantity' => $quantity, |
| 98 |
'price' => edd_get_download_price( $download_id ), |
| 99 |
'tax_class' => edd_get_cart_tax_rate(), |
| 100 |
'cart_total_items' => edd_get_cart_quantity(), |
| 101 |
'cart_total' => edd_get_cart_total(), |
| 102 |
] |
| 103 |
); |
| 104 |
|
| 105 |
$proxy = new Proxy( false ); |
| 106 |
|
| 107 |
$proxy->do_request( $this->event_goals['add-to-cart'], null, null, $props ); |
| 108 |
} |
| 109 |
|
| 110 |
/** |
| 111 |
* Tracks the removal of an item from the cart, updates cart contents and triggers an event to log this action. |
| 112 |
* |
| 113 |
* @param string|int $key The key of the item in the cart to be removed. |
| 114 |
* |
| 115 |
* @return void |
| 116 |
* |
| 117 |
* @codeCoverageIgnore Because we can't test XHR requests here. |
| 118 |
*/ |
| 119 |
public function track_remove_cart_item( $key ) { |
| 120 |
$cart_contents = edd_get_cart_contents(); |
| 121 |
$item_removed_from_cart = $cart_contents[ $key ] ?? []; |
| 122 |
$product = null; |
| 123 |
|
| 124 |
if ( empty( $item_removed_from_cart ) ) { |
| 125 |
return; |
| 126 |
} |
| 127 |
|
| 128 |
unset( $cart_contents[ $key ] ); |
| 129 |
|
| 130 |
if ( isset( $item_removed_from_cart['id'] ) ) { |
| 131 |
$product = new \EDD_Download( $item_removed_from_cart['id'] ); |
| 132 |
} |
| 133 |
|
| 134 |
if ( ! $product ) { |
| 135 |
return; |
| 136 |
} |
| 137 |
|
| 138 |
$total_removed_from_cart = edd_get_cart_total() - ( $product->get_price() * $item_removed_from_cart['quantity'] ); |
| 139 |
|
| 140 |
$props = apply_filters( |
| 141 |
'plausible_analytics_edd_remove_cart_item_custom_properties', |
| 142 |
[ |
| 143 |
'product_name' => $product->get_name(), |
| 144 |
'product_id' => $item_removed_from_cart['id'], |
| 145 |
'quantity' => $item_removed_from_cart['quantity'], |
| 146 |
'cart_total_items' => count( $cart_contents ), |
| 147 |
'cart_total' => $total_removed_from_cart, |
| 148 |
] |
| 149 |
); |
| 150 |
|
| 151 |
$proxy = new Proxy( false ); |
| 152 |
|
| 153 |
$proxy->do_request( $this->event_goals['remove-from-cart'], null, null, $props ); |
| 154 |
} |
| 155 |
|
| 156 |
/** |
| 157 |
* Tracks the "entered checkout" event with relevant cart data. |
| 158 |
* |
| 159 |
* @return void |
| 160 |
* |
| 161 |
* @codeCoverageIgnore Because we can't test XHR requests here. |
| 162 |
*/ |
| 163 |
public function track_entered_checkout() { |
| 164 |
// Just to make sure we're where we're supposed to be. |
| 165 |
if ( ! edd_is_checkout() ) { |
| 166 |
return; |
| 167 |
} |
| 168 |
|
| 169 |
$props = apply_filters( |
| 170 |
'plausible_analytics_edd_entered_checkout_custom_properties', |
| 171 |
[ |
| 172 |
// @todo Add cart contents |
| 173 |
'subtotal' => edd_get_cart_subtotal(), |
| 174 |
'tax' => edd_get_cart_tax(), |
| 175 |
'total' => edd_get_cart_total(), |
| 176 |
] |
| 177 |
); |
| 178 |
|
| 179 |
$proxy = new Proxy( false ); |
| 180 |
|
| 181 |
$proxy->do_request( $this->event_goals['checkout'], null, null, $props ); |
| 182 |
} |
| 183 |
|
| 184 |
/** |
| 185 |
* Tracks the "purchase" event with relevant payment data. |
| 186 |
* |
| 187 |
* We choose to render a JS script, instead of hooking into an action, because user-agent information |
| 188 |
* gets lost when the user is first redirected to a Payment Provider. |
| 189 |
* |
| 190 |
* @return void |
| 191 |
*/ |
| 192 |
public function track_purchase() { |
| 193 |
if ( ! edd_is_success_page() ) { |
| 194 |
return; // @codeCoverageIgnore |
| 195 |
} |
| 196 |
|
| 197 |
$session = edd_get_purchase_session(); |
| 198 |
$order = null; |
| 199 |
|
| 200 |
if ( ! empty( $session['purchase_key'] ) ) { |
| 201 |
$order = edd_get_order_by( 'payment_key', $session['purchase_key'] ); |
| 202 |
} |
| 203 |
|
| 204 |
// Don't track on page reload. |
| 205 |
if ( ! $order || edd_get_order_meta( $order->id, Integrations::PURCHASE_TRACKED_META_KEY, true ) ) { |
| 206 |
return; |
| 207 |
} |
| 208 |
|
| 209 |
$props = wp_json_encode( |
| 210 |
apply_filters( |
| 211 |
'plausible_analytics_edd_purchase_custom_properties', |
| 212 |
[ |
| 213 |
EnhancedMeasurements::ECOMMERCE_REVENUE => [ |
| 214 |
'amount' => $order->total, |
| 215 |
'currency' => $order->currency, |
| 216 |
], |
| 217 |
] |
| 218 |
) |
| 219 |
); |
| 220 |
$label = $this->event_goals['purchase']; |
| 221 |
|
| 222 |
echo sprintf( Integrations::SCRIPT_WRAPPER, "window.plausible( '$label', $props )" ); |
| 223 |
|
| 224 |
edd_add_order_meta( $order->id, Integrations::PURCHASE_TRACKED_META_KEY, true ); |
| 225 |
} |
| 226 |
} |
| 227 |
|