PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.4.2
Jetpack – WP Security, Backup, Speed, & Growth v12.4.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / woocommerce-analytics / classes / class-jetpack-woocommerce-analytics-universal.php
jetpack / modules / woocommerce-analytics / classes Last commit date
class-jetpack-woocommerce-analytics-universal.php 3 years ago
class-jetpack-woocommerce-analytics-universal.php
640 lines
1 <?php
2 /**
3 * Jetpack_WooCommerce_Analytics_Universal
4 *
5 * @package automattic/jetpack
6 * @author Automattic
7 */
8
9 use Automattic\Jetpack\Assets;
10
11 /**
12 * Bail if accessed directly
13 */
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 /**
19 * Class Jetpack_WooCommerce_Analytics_Universal
20 * Filters and Actions added to Store pages to perform analytics
21 */
22 class Jetpack_WooCommerce_Analytics_Universal {
23 /**
24 * Jetpack_WooCommerce_Analytics_Universal constructor.
25 */
26 public function __construct() {
27 // loading _wca.
28 add_action( 'wp_head', array( $this, 'wp_head_top' ), 1 );
29
30 // add to carts from non-product pages or lists -- search, store etc.
31 add_action( 'wp_head', array( $this, 'loop_session_events' ), 2 );
32
33 // loading s.js.
34 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_tracking_script' ) );
35
36 // Capture cart events.
37 add_action( 'woocommerce_add_to_cart', array( $this, 'capture_add_to_cart' ), 10, 6 );
38
39 // single product page view.
40 add_action( 'woocommerce_after_single_product', array( $this, 'capture_product_view' ) );
41
42 add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) );
43 add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) );
44 add_action( 'wcct_before_cart_widget', array( $this, 'remove_from_cart' ) );
45 add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 );
46
47 // Checkout.
48 // Send events after checkout template (shortcode).
49 add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) );
50 // Send events after checkout block.
51 add_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after', array( $this, 'checkout_process' ) );
52
53 // order confirmed.
54 add_action( 'woocommerce_thankyou', array( $this, 'order_process' ), 10, 1 );
55 add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart_via_quantity' ), 10, 1 );
56
57 add_filter( 'woocommerce_checkout_posted_data', array( $this, 'save_checkout_post_data' ), 10, 1 );
58 }
59
60 /**
61 * Make _wca available to queue events
62 */
63 public function wp_head_top() {
64 if ( is_cart() || is_checkout() || is_checkout_pay_page() || is_order_received_page() || is_add_payment_method_page() ) {
65 echo '<script>window._wca_prevent_referrer = true;</script>' . "\r\n";
66 }
67 echo '<script>window._wca = window._wca || [];</script>' . "\r\n";
68 }
69
70 /**
71 * Place script to call s.js, Store Analytics.
72 */
73 public function enqueue_tracking_script() {
74 $filename = sprintf(
75 'https://stats.wp.com/s-%d.js',
76 gmdate( 'YW' )
77 );
78
79 Assets::enqueue_async_script( 'woocommerce-analytics', esc_url( $filename ), esc_url( $filename ), array(), null, false );
80 }
81
82 /**
83 * Default event properties which should be included with all events.
84 *
85 * @return array Array of standard event props.
86 */
87 public function get_common_properties() {
88 $site_info = array(
89 'blog_id' => Jetpack::get_option( 'id' ),
90 'ui' => $this->get_user_id(),
91 'url' => home_url(),
92 'woo_version' => WC()->version,
93 );
94 $cart_checkout_info = self::get_cart_checkout_info();
95 return array_merge( $site_info, $cart_checkout_info );
96 }
97
98 /**
99 * Render tracks event properties as string of JavaScript object props.
100 *
101 * @param array $properties Array of key/value pairs.
102 * @return string String of the form "key1: value1, key2: value2, " (etc).
103 */
104 private function render_properties_as_js( $properties ) {
105 $js_args_string = '';
106 foreach ( $properties as $key => $value ) {
107 if ( is_array( $value ) ) {
108 $js_args_string = $js_args_string . "'$key': " . wp_json_encode( $value ) . ',';
109 } else {
110 $js_args_string = $js_args_string . "'$key': '" . esc_js( $value ) . "', ";
111 }
112 }
113 return $js_args_string;
114 }
115
116 /**
117 * Record an event with optional custom properties.
118 *
119 * @param string $event_name The name of the event to record.
120 * @param integer $product_id The id of the product relating to the event.
121 * @param array $properties Optional array of (key => value) event properties.
122 *
123 * @return string|void
124 */
125 public function record_event( $event_name, $product_id, $properties = array() ) {
126 $js = $this->process_event_properties( $event_name, $product_id, $properties );
127 wc_enqueue_js( "_wca.push({$js});" );
128 }
129
130 /**
131 * Compose event properties.
132 *
133 * @param string $event_name The name of the event to record.
134 * @param integer $product_id The id of the product relating to the event.
135 * @param array $properties Optional array of (key => value) event properties.
136 *
137 * @return string|void
138 */
139 public function process_event_properties( $event_name, $product_id, $properties = array() ) {
140 $product = wc_get_product( $product_id );
141 if ( ! $product instanceof WC_Product ) {
142 return;
143 }
144 $product_details = $this->get_product_details( $product );
145
146 $all_props = array_merge(
147 $properties,
148 $this->get_common_properties()
149 );
150
151 $js = "{
152 '_en': '" . esc_js( $event_name ) . "',
153 'pi': '" . esc_js( $product_id ) . "',
154 'pn': '" . esc_js( $product_details['name'] ) . "',
155 'pc': '" . esc_js( $product_details['category'] ) . "',
156 'pp': '" . esc_js( $product_details['price'] ) . "',
157 'pt': '" . esc_js( $product_details['type'] ) . "'," .
158 $this->render_properties_as_js( $all_props ) . '
159 }';
160
161 return $js;
162 }
163
164 /**
165 * On product lists or other non-product pages, add an event listener to "Add to Cart" button click
166 */
167 public function loop_session_events() {
168 // Check for previous events queued in session data.
169 if ( is_object( WC()->session ) ) {
170 $data = WC()->session->get( 'wca_session_data' );
171 if ( ! empty( $data ) ) {
172 foreach ( $data as $data_instance ) {
173 $this->record_event(
174 $data_instance['event'],
175 $data_instance['product_id'],
176 array(
177 'pq' => $data_instance['quantity'],
178 )
179 );
180 }
181 // Clear data, now that these events have been recorded.
182 WC()->session->set( 'wca_session_data', '' );
183 }
184 }
185 }
186
187 /**
188 * On the cart page, add an event listener for removal of product click
189 */
190 public function remove_from_cart() {
191 $common_props = $this->render_properties_as_js(
192 $this->get_common_properties()
193 );
194
195 // We listen at div.woocommerce because the cart 'form' contents get forcibly
196 // updated and subsequent removals from cart would then not have this click
197 // handler attached.
198 wc_enqueue_js(
199 "jQuery( 'div.woocommerce' ).on( 'click', 'a.remove', function() {
200 var productID = jQuery( this ).data( 'product_id' );
201 var quantity = jQuery( this ).parent().parent().find( '.qty' ).val()
202 var productDetails = {
203 'id': productID,
204 'quantity': quantity ? quantity : '1',
205 };
206 _wca.push( {
207 '_en': 'woocommerceanalytics_remove_from_cart',
208 'pi': productDetails.id,
209 'pq': productDetails.quantity, " .
210 $common_props . '
211 } );
212 } );'
213 );
214 }
215
216 /**
217 * Adds the product ID to the remove product link (for use by remove_from_cart above) if not present
218 *
219 * @param string $url Full HTML a tag of the link to remove an item from the cart.
220 * @param string $key Unique Key ID for a cart item.
221 *
222 * @return mixed.
223 */
224 public function remove_from_cart_attributes( $url, $key ) {
225 if ( false !== strpos( $url, 'data-product_id' ) ) {
226 return $url;
227 }
228
229 $item = WC()->cart->get_cart_item( $key );
230 $product = $item['data'];
231
232 $new_attributes = sprintf(
233 '" data-product_id="%s">',
234 esc_attr( $product->get_id() )
235 );
236
237 $url = str_replace( '">', $new_attributes, $url );
238 return $url;
239 }
240
241 /**
242 * Gather relevant product information
243 *
244 * @param array $product product.
245 * @return array
246 */
247 public function get_product_details( $product ) {
248 return array(
249 'id' => $product->get_id(),
250 'name' => $product->get_title(),
251 'category' => $this->get_product_categories_concatenated( $product ),
252 'price' => $product->get_price(),
253 'type' => $product->get_type(),
254 );
255 }
256
257 /**
258 * Track a product page view
259 */
260 public function capture_product_view() {
261 global $product;
262 if ( ! $product instanceof WC_Product ) {
263 return;
264 }
265
266 $this->record_event(
267 'woocommerceanalytics_product_view',
268 $product->get_id()
269 );
270 }
271
272 /**
273 * On the Checkout page, trigger an event for each product in the cart
274 */
275 public function checkout_process() {
276 $cart = WC()->cart->get_cart();
277
278 $guest_checkout = ucfirst( get_option( 'woocommerce_enable_guest_checkout', 'No' ) );
279 $create_account = ucfirst( get_option( 'woocommerce_enable_signup_and_login_from_checkout', 'No' ) );
280
281 $enabled_payment_options = array_filter(
282 WC()->payment_gateways->get_available_payment_gateways(),
283 function ( $payment_gateway ) {
284 if ( ! $payment_gateway instanceof WC_Payment_Gateway ) {
285 return false;
286 }
287
288 return $payment_gateway->is_available();
289 }
290 );
291
292 $enabled_payment_options = array_keys( $enabled_payment_options );
293 $include_express_payment = false;
294
295 $wcpay_version = get_option( 'woocommerce_woocommerce_payments_version' );
296 $has_required_wcpay_version = version_compare( $wcpay_version, '2.9.0', '>=' );
297 // Check express payment availablity only if WC Pay is enabled and express checkout (payment request) is enabled.
298 if ( in_array( 'woocommerce_payments', $enabled_payment_options, true ) && $has_required_wcpay_version ) {
299 $wcpay_settings = get_option( 'woocommerce_woocommerce_payments_settings', array() );
300 if ( array_key_exists( 'payment_request', $wcpay_settings ) && 'yes' === $wcpay_settings['payment_request'] ) {
301 $include_express_payment = true;
302 }
303 }
304
305 foreach ( $cart as $cart_item_key => $cart_item ) {
306 /**
307 * This filter is already documented in woocommerce/templates/cart/cart.php
308 */
309 $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
310
311 if ( ! $product || ! $product instanceof WC_Product ) {
312 continue;
313 }
314
315 if ( true === $include_express_payment ) {
316 $properties = $this->process_event_properties(
317 'woocommerceanalytics_product_checkout',
318 $product->get_id(),
319 array(
320 'pq' => $cart_item['quantity'],
321 'payment_options' => $enabled_payment_options,
322 'device' => wp_is_mobile() ? 'mobile' : 'desktop',
323 'guest_checkout' => 'Yes' === $guest_checkout ? 'Yes' : 'No',
324 'create_account' => 'Yes' === $create_account ? 'Yes' : 'No',
325 'express_checkout' => 'null',
326 )
327 );
328 wc_enqueue_js(
329 "
330 // wcpay.payment-request.availability event gets fired twice.
331 // make sure we push only one event.
332 var cartItem_{$cart_item_key}_logged = false;
333 wp.hooks.addAction('wcpay.payment-request.availability', 'wcpay', function(args) {
334 if ( true === cartItem_{$cart_item_key}_logged ) {
335 return;
336 }
337 var properties = {$properties};
338 properties.express_checkout = args.paymentRequestType;
339 _wca.push(properties);
340 cartItem_{$cart_item_key}_logged = true;
341 });
342 "
343 );
344 } else {
345 $this->record_event(
346 'woocommerceanalytics_product_checkout',
347 $product->get_id(),
348 array(
349 'pq' => $cart_item['quantity'],
350 'payment_options' => $enabled_payment_options,
351 'device' => wp_is_mobile() ? 'mobile' : 'desktop',
352 'guest_checkout' => 'Yes' === $guest_checkout ? 'Yes' : 'No',
353 'create_account' => 'Yes' === $create_account ? 'Yes' : 'No',
354 'express_checkout' => 'null',
355 )
356 );
357 }
358 }
359 }
360
361 /**
362 * After the checkout process, fire an event for each item in the order
363 *
364 * @param string $order_id Order Id.
365 */
366 public function order_process( $order_id ) {
367 $order = wc_get_order( $order_id );
368
369 if (
370 ! $order
371 || ! $order instanceof WC_Order
372 ) {
373 return;
374 }
375
376 $payment_option = $order->get_payment_method();
377
378 if ( is_object( WC()->session ) ) {
379 $create_account = true === WC()->session->get( 'wc_checkout_createaccount_used' ) ? 'Y' : 'N';
380 } else {
381 $create_account = 'N';
382 }
383
384 $guest_checkout = $order->get_user() ? 'N' : 'Y';
385
386 $express_checkout = 'null';
387 // When the payment option is woocommerce_payment
388 // See if Google Pay or Apple Pay was used.
389 if ( 'woocommerce_payments' === $payment_option ) {
390 $payment_option_title = $order->get_payment_method_title();
391 if ( 'Google Pay (WooCommerce Payments)' === $payment_option_title ) {
392 $express_checkout = array( 'google_pay' );
393 } elseif ( 'Apple Pay (WooCommerce Payments)' === $payment_option_title ) {
394 $express_checkout = array( 'apple_pay' );
395 }
396 }
397
398 // loop through products in the order and queue a purchase event.
399 foreach ( $order->get_items() as $order_item ) {
400 $product_id = is_callable( array( $order_item, 'get_product_id' ) ) ? $order_item->get_product_id() : -1;
401
402 $this->record_event(
403 'woocommerceanalytics_product_purchase',
404 $product_id,
405 array(
406 'oi' => $order->get_order_number(),
407 'pq' => $order_item->get_quantity(),
408 'device' => wp_is_mobile() ? 'mobile' : 'desktop',
409 'payment_option' => $payment_option,
410 'create_account' => $create_account,
411 'guest_checkout' => $guest_checkout,
412 'express_checkout' => $express_checkout,
413 )
414 );
415 }
416 }
417
418 /**
419 * Listen for clicks on the "Update Cart" button to know if an item has been removed by
420 * updating its quantity to zero
421 */
422 public function remove_from_cart_via_quantity() {
423 $common_props = $this->render_properties_as_js(
424 $this->get_common_properties()
425 );
426
427 wc_enqueue_js(
428 "
429 jQuery( 'button[name=update_cart]' ).on( 'click', function() {
430 var cartItems = jQuery( '.cart_item' );
431 cartItems.each( function( item ) {
432 var qty = jQuery( this ).find( 'input.qty' );
433 if ( qty && qty.val() === '0' ) {
434 var productID = jQuery( this ).find( '.product-remove a' ).data( 'product_id' );
435 _wca.push( {
436 '_en': 'woocommerceanalytics_remove_from_cart',
437 'pi': productID, " .
438 $common_props . '
439 } );
440 }
441 } );
442 } );'
443 );
444 }
445
446 /**
447 * Get the current user id
448 *
449 * @return int
450 */
451 public function get_user_id() {
452 if ( is_user_logged_in() ) {
453 $blogid = Jetpack::get_option( 'id' );
454 $userid = get_current_user_id();
455 return $blogid . ':' . $userid;
456 }
457 return 'null';
458 }
459
460 /**
461 * Track adding items to the cart.
462 *
463 * @param string $cart_item_key Cart item key.
464 * @param int $product_id Product added to cart.
465 * @param int $quantity Quantity added to cart.
466 * @param int $variation_id Product variation.
467 * @param array $variation Variation attributes..
468 * @param array $cart_item_data Other cart data.
469 */
470 public function capture_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
471 $referer_postid = isset( $_SERVER['HTTP_REFERER'] ) ? url_to_postid( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) ) : 0;
472 // if the referring post is not a product OR the product being added is not the same as post.
473 // (eg. related product list on single product page) then include a product view event.
474 $product_by_referer_postid = wc_get_product( $referer_postid );
475 if ( ! $product_by_referer_postid instanceof WC_Product || (int) $product_id !== $referer_postid ) {
476 $this->capture_event_in_session_data( $product_id, $quantity, 'woocommerceanalytics_product_view' );
477 }
478 // add cart event to the session data.
479 $this->capture_event_in_session_data( $product_id, $quantity, 'woocommerceanalytics_add_to_cart' );
480 }
481
482 /**
483 * Track in-session data.
484 *
485 * @param int $product_id Product ID.
486 * @param int $quantity Quantity.
487 * @param string $event Fired event.
488 */
489 public function capture_event_in_session_data( $product_id, $quantity, $event ) {
490
491 $product = wc_get_product( $product_id );
492 if ( ! $product instanceof WC_Product ) {
493 return;
494 }
495
496 $quantity = ( 0 === $quantity ) ? 1 : $quantity;
497
498 // check for existing data.
499 if ( is_object( WC()->session ) ) {
500 $data = WC()->session->get( 'wca_session_data' );
501 if ( empty( $data ) || ! is_array( $data ) ) {
502 $data = array();
503 }
504 } else {
505 $data = array();
506 }
507
508 // extract new event data.
509 $new_data = array(
510 'event' => $event,
511 'product_id' => (string) $product_id,
512 'quantity' => (string) $quantity,
513 );
514
515 // append new data.
516 $data[] = $new_data;
517
518 WC()->session->set( 'wca_session_data', $data );
519 }
520
521 /**
522 * Gets product categories or varation attributes as a formatted concatenated string
523 *
524 * @param object $product WC_Product.
525 * @return string
526 */
527 public function get_product_categories_concatenated( $product ) {
528
529 if ( ! $product instanceof WC_Product ) {
530 return '';
531 }
532
533 $variation_data = $product->is_type( 'variation' ) ? wc_get_product_variation_attributes( $product->get_id() ) : '';
534 if ( is_array( $variation_data ) && ! empty( $variation_data ) ) {
535 $line = wc_get_formatted_variation( $variation_data, true );
536 } else {
537 $out = array();
538 $categories = get_the_terms( $product->get_id(), 'product_cat' );
539 if ( $categories ) {
540 foreach ( $categories as $category ) {
541 $out[] = $category->name;
542 }
543 }
544 $line = implode( '/', $out );
545 }
546 return $line;
547 }
548
549 /**
550 * Search a specific post for text content.
551 *
552 * Note: similar code is in a WooCommerce core PR:
553 * https://github.com/woocommerce/woocommerce/pull/25932
554 *
555 * @param integer $post_id The id of the post to search.
556 * @param string $text The text to search for.
557 * @return integer 1 if post contains $text (otherwise 0).
558 */
559 public static function post_contains_text( $post_id, $text ) {
560 global $wpdb;
561
562 // Search for the text anywhere in the post.
563 $wildcarded = "%{$text}%";
564
565 $result = $wpdb->get_var(
566 $wpdb->prepare(
567 "
568 SELECT COUNT( * ) FROM {$wpdb->prefix}posts
569 WHERE ID=%d
570 AND {$wpdb->prefix}posts.post_content LIKE %s
571 ",
572 array( $post_id, $wildcarded )
573 )
574 );
575
576 return ( '0' !== $result ) ? 1 : 0;
577 }
578
579 /**
580 * Get info about the cart & checkout pages, in particular
581 * whether the store is using shortcodes or Gutenberg blocks.
582 * This info is cached in a transient.
583 *
584 * Note: similar code is in a WooCommerce core PR:
585 * https://github.com/woocommerce/woocommerce/pull/25932
586 *
587 * @return array
588 */
589 public static function get_cart_checkout_info() {
590 $transient_name = 'jetpack_woocommerce_analytics_cart_checkout_info_cache';
591
592 $info = get_transient( $transient_name );
593 if ( false === $info ) {
594 $cart_page_id = wc_get_page_id( 'cart' );
595 $checkout_page_id = wc_get_page_id( 'checkout' );
596
597 $info = array(
598 'cart_page_contains_cart_block' => self::post_contains_text(
599 $cart_page_id,
600 '<!-- wp:woocommerce/cart'
601 ),
602 'cart_page_contains_cart_shortcode' => self::post_contains_text(
603 $cart_page_id,
604 '[woocommerce_cart]'
605 ),
606 'checkout_page_contains_checkout_block' => self::post_contains_text(
607 $checkout_page_id,
608 '<!-- wp:woocommerce/checkout'
609 ),
610 'checkout_page_contains_checkout_shortcode' => self::post_contains_text(
611 $checkout_page_id,
612 '[woocommerce_checkout]'
613 ),
614 );
615
616 set_transient( $transient_name, $info, DAY_IN_SECONDS );
617 }
618
619 return $info;
620 }
621
622 /**
623 * Save createaccount post data to be used in $this->order_process.
624 *
625 * @param array $data post data from the checkout page.
626 *
627 * @return array
628 */
629 public function save_checkout_post_data( array $data ) {
630 $session = WC()->session;
631 if ( is_object( $session ) ) {
632 if ( isset( $data['createaccount'] ) && ! empty( $data['createaccount'] ) ) {
633 $session->set( 'wc_checkout_createaccount_used', true );
634 $session->save_data();
635 }
636 }
637 return $data;
638 }
639 }
640