-
Notifications
You must be signed in to change notification settings - Fork 70
/
Copy pathclass-wc-abstract-google-analytics-js.php
380 lines (343 loc) · 10.6 KB
/
class-wc-abstract-google-analytics-js.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema;
use Automattic\WooCommerce\StoreApi\Schemas\V1\CartItemSchema;
/**
* WC_Abstract_Google_Analytics_JS class
*
* Abstract JS for recording Google Analytics/Gtag info
*/
abstract class WC_Abstract_Google_Analytics_JS {
/** @var WC_Abstract_Google_Analytics_JS $instance Class Instance */
protected static $instance;
/** @var array $settings Inherited Analytics settings */
protected static $settings;
/** @var string Developer ID */
public const DEVELOPER_ID = 'dOGY3NW';
/**
* Constructor
* To be called from child classes to setup event data
*
* @return void
*/
public function __construct() {
$this->attach_event_data();
if ( did_action( 'woocommerce_blocks_loaded' ) ) {
woocommerce_store_api_register_endpoint_data(
array(
'endpoint' => ProductSchema::IDENTIFIER,
'namespace' => 'woocommerce_google_analytics_integration',
'data_callback' => array( $this, 'data_callback' ),
'schema_callback' => array( $this, 'schema_callback' ),
'schema_type' => ARRAY_A,
)
);
woocommerce_store_api_register_endpoint_data(
array(
'endpoint' => CartItemSchema::IDENTIFIER,
'namespace' => 'woocommerce_google_analytics_integration',
'data_callback' => array( $this, 'data_callback' ),
'schema_callback' => array( $this, 'schema_callback' ),
'schema_type' => ARRAY_A,
)
);
}
}
/**
* Hook into various parts of WooCommerce and set the relevant
* script data that the frontend tracking script will use.
*
* @return void
*/
public function attach_event_data(): void {
add_action(
'wp_head',
function () {
$this->set_script_data( 'cart', $this->get_formatted_cart() );
}
);
add_action(
'woocommerce_before_single_product',
function () {
global $product;
$this->set_script_data( 'product', $this->get_formatted_product( $product ) );
}
);
add_action(
'woocommerce_add_to_cart',
function ( $cart_item_key, $product_id, $quantity, $variation_id, $variation ) {
$this->set_script_data( 'added_to_cart', $this->get_formatted_product( wc_get_product( $product_id ), $variation_id, $variation ) );
},
10,
5
);
add_filter(
'woocommerce_loop_add_to_cart_link',
function ( $button, $product ) {
$this->append_script_data( 'products', $this->get_formatted_product( $product ) );
return $button;
},
10,
2
);
add_action(
'woocommerce_thankyou',
function ( $order_id ) {
if ( 'yes' === self::get( 'ga_ecommerce_tracking_enabled' ) ) {
$order = wc_get_order( $order_id );
if ( $order && $order->get_meta( '_ga_tracked' ) !== '1' ) {
// Check order key.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$order_key = empty( $_GET['key'] ) ? '' : wc_clean( wp_unslash( $_GET['key'] ) );
if ( $order->key_is_valid( $order_key ) ) {
// Mark the order as tracked.
$order->update_meta_data( '_ga_tracked', 1 );
$order->save();
$this->set_script_data( 'order', $this->get_formatted_order( $order ) );
}
}
}
}
);
}
/**
* Return one of our settings
*
* @param string $setting Key/name for the setting.
*
* @return string|null Value of the setting or null if not found
*/
protected static function get( $setting ): ?string {
return self::$settings[ $setting ] ?? null;
}
/**
* Generic GA snippet for opt out
*/
public static function load_opt_out(): void {
$code = "
var gaProperty = '" . esc_js( self::get( 'ga_id' ) ) . "';
var disableStr = 'ga-disable-' + gaProperty;
if ( document.cookie.indexOf( disableStr + '=true' ) > -1 ) {
window[disableStr] = true;
}
function gaOptout() {
document.cookie = disableStr + '=true; expires=Thu, 31 Dec 2099 23:59:59 UTC; path=/';
window[disableStr] = true;
}";
wp_register_script( 'google-analytics-opt-out', '', array(), null, false );
wp_add_inline_script( 'google-analytics-opt-out', $code );
wp_enqueue_script( 'google-analytics-opt-out' );
}
/**
* Get item identifier from product data
*
* @param WC_Product $product WC_Product Object.
*
* @return string
*/
public static function get_product_identifier( WC_Product $product ): string {
$identifier = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id();
if ( 'product_sku' === self::get( 'ga_product_identifier' ) ) {
if ( ! empty( $product->get_sku() ) ) {
$identifier = $product->get_sku();
} else {
$identifier = '#' . ( $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id() );
}
}
return apply_filters( 'woocommerce_ga_product_identifier', $identifier, $product );
}
/**
* Returns an array of cart data in the required format
*
* @return array
*/
public function get_formatted_cart(): array {
return array(
'items' => array_map(
function ( $item ) {
return array_merge(
$this->get_formatted_product( $item['data'] ),
array(
'quantity' => $item['quantity'],
'prices' => array(
'price' => $this->get_formatted_price( $item['line_total'] ),
'currency_minor_unit' => wc_get_price_decimals(),
),
)
);
},
array_values( WC()->cart->get_cart() )
),
'coupons' => WC()->cart->get_coupons(),
'totals' => array(
'currency_code' => get_woocommerce_currency(),
'total_price' => $this->get_formatted_price( WC()->cart->get_total( 'edit' ) ),
'currency_minor_unit' => wc_get_price_decimals(),
),
);
}
/**
* Returns an array of product data in the required format
*
* @param WC_Product $product The product to format.
* @param int $variation_id Variation product ID.
* @param array|bool $variation An array containing product variation attributes to include in the product data.
* For the "variation" type products, we'll use product->get_attributes.
*
* @return array
*/
public function get_formatted_product( WC_Product $product, $variation_id = 0, $variation = false ): array {
$product_id = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id();
$price = $product->get_price();
// Get product price from chosen variation if set.
if ( $variation_id ) {
$variation_product = wc_get_product( $variation_id );
if ( $variation_product ) {
$price = $variation_product->get_price();
}
}
$formatted = array(
'id' => $product_id,
'name' => $product->get_title(),
'categories' => array_map(
fn( $category ) => array( 'name' => $category->name ),
wc_get_product_terms( $product_id, 'product_cat', array( 'number' => 5 ) )
),
'prices' => array(
'price' => $this->get_formatted_price( $price ),
'currency_minor_unit' => wc_get_price_decimals(),
),
'extensions' => array(
'woocommerce_google_analytics_integration' => array(
'identifier' => $this->get_product_identifier( $product ),
),
),
);
if ( $product->is_type( 'variation' ) ) {
$variation = $product->get_attributes();
}
if ( is_array( $variation ) ) {
$formatted['variation'] = implode(
', ',
array_map(
function ( $attribute, $value ) {
return sprintf(
'%s: %s',
str_replace( 'attribute_', '', $attribute ),
$value
);
},
array_keys( $variation ),
array_values( $variation )
)
);
}
return $formatted;
}
/**
* Returns an array of order data in the required format
*
* @param WC_Abstract_Order $order An instance of the WooCommerce Order object.
*
* @return array
*/
public function get_formatted_order( $order ): array {
return array(
'id' => $order->get_id(),
'affiliation' => get_bloginfo( 'name' ),
'totals' => array(
'currency_code' => $order->get_currency(),
'currency_minor_unit' => wc_get_price_decimals(),
'tax_total' => $this->get_formatted_price( $order->get_total_tax() ),
'shipping_total' => $this->get_formatted_price( $order->get_total_shipping() ),
'total_price' => $this->get_formatted_price( $order->get_total() ),
),
'items' => array_map(
function ( $item ) {
return array_merge(
$this->get_formatted_product( $item->get_product() ),
array(
'quantity' => $item->get_quantity(),
)
);
},
array_values( $order->get_items() ),
),
);
}
/**
* Formats a price the same way WooCommerce Blocks does
*
* @param mixed $value The price value for format
*
* @return int
*/
public function get_formatted_price( $value ): int {
return intval(
round(
( (float) wc_format_decimal( $value ) ) * ( 10 ** absint( wc_get_price_decimals() ) ),
0
)
);
}
/**
* Add product identifier to StoreAPI
*
* @param WC_Product|array $product Either an instance of WC_Product or a cart item array depending on the endpoint
*
* @return array
*/
public function data_callback( $product ): array {
$product = is_a( $product, 'WC_Product' ) ? $product : $product['data'];
return array(
'identifier' => (string) $this->get_product_identifier( $product ),
);
}
/**
* Schema for the extended StoreAPI data
*
* @return array
*/
public function schema_callback(): array {
return array(
'identifier' => array(
'description' => __( 'The formatted product identifier to use in Google Analytics events.', 'woocommerce-google-analytics-integration' ),
'type' => 'string',
'readonly' => true,
),
);
}
/**
* Returns the tracker variable this integration should use
*
* @return string
*/
abstract public static function tracker_function_name(): string;
/**
* Add an event to the script data
*
* @param string $type The type of event this data is related to.
* @param string|array $data The event data to add.
*
* @return void
*/
abstract public function set_script_data( string $type, $data ): void;
/**
* Append data to an existing script data array
*
* @param string $type The type of event this data is related to.
* @param string|array $data The event data to add.
*
* @return void
*/
abstract public function append_script_data( string $type, $data ): void;
/**
* Get the class instance
*
* @param array $settings Settings
* @return WC_Abstract_Google_Analytics_JS
*/
abstract public static function get_instance( $settings = array() ): WC_Abstract_Google_Analytics_JS;
}