PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 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 All 508 releases
← All changes | jetpack_vendor/automattic/woocommerce-analytics/src/class-woo-analytics-trait.php +332 -165 13.5.2 → 16.3 View file →
@@ -6,8 +6,9 @@
6 6 */
7 7
8 8 namespace Automattic\Woocommerce_Analytics;
9 9
10 +use Automattic\Block_Scanner;
10 11 use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
11 12 use WC_Order_Item;
12 13 use WC_Order_Item_Product;
13 14 use WC_Payment_Gateway;
@@ -52,8 +53,16 @@
52 53 */
53 54 protected $additional_blocks_on_checkout_page;
54 55
55 56 /**
57 + * Locks Add to Cart Events Tracking in the current request avoiding duplications.
58 + * i.e. If update_cart and add_to_cart actions happens in the same request.
59 + *
60 + * @var bool If true. Cart events are locked for the current request.
61 + */
62 + protected $lock_add_to_cart_events = false;
63 +
64 + /**
56 65 * Format Cart Items or Order Items to an array
57 66 *
58 67 * @param array|WC_Order_Item[] $items Cart Items or Order Items.
59 68 */
@@ -80,9 +89,9 @@
80 89 }
81 90 $products[] = $data;
82 91 }
83 92
84 - return wp_json_encode( $products );
93 + return wp_json_encode( $products, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG );
85 94 }
86 95
87 96 /**
88 97 * Get Cart/Checkout page view shared data
@@ -89,10 +98,11 @@
89 98 */
90 99 protected function get_cart_checkout_shared_data() {
91 100 $cart = WC()->cart;
92 101
93 - $guest_checkout = ucfirst( get_option( 'woocommerce_enable_guest_checkout', 'No' ) );
94 - $create_account = ucfirst( get_option( 'woocommerce_enable_signup_and_login_from_checkout', 'No' ) );
102 + $guest_checkout = ucfirst( get_option( 'woocommerce_enable_guest_checkout', 'No' ) );
103 + $create_account = ucfirst( get_option( 'woocommerce_enable_signup_and_login_from_checkout', 'No' ) );
104 + $delayed_account_creation = ucfirst( get_option( 'woocommerce_enable_delayed_account_creation', 'Yes' ) );
95 105
96 106 $coupons = $cart->get_coupons();
97 107 $coupon_used = 0;
98 108 if ( is_countable( $coupons ) ) {
@@ -110,19 +120,17 @@
110 120 }
111 121 );
112 122
113 123 $enabled_payment_options = array_keys( $enabled_payment_options );
114 - $cart_total = wc_prices_include_tax() ? $cart->get_cart_contents_total() + $cart->get_cart_contents_tax() : $cart->get_cart_contents_total();
115 124 $shared_data = array(
116 - 'products' => $this->format_items_to_json( $cart->get_cart() ),
117 - 'create_account' => $create_account,
118 - 'guest_checkout' => $guest_checkout,
119 - 'express_checkout' => 'null', // TODO: not solved yet.
120 - 'products_count' => $cart->get_cart_contents_count(),
121 - 'order_value' => $cart_total,
122 - 'shipping_options_count' => 'null', // TODO: not solved yet.
123 - 'coupon_used' => $coupon_used,
124 - 'payment_options' => $enabled_payment_options,
125 + 'products' => $this->format_items_to_json( $cart->get_cart() ),
126 + 'create_account' => $create_account,
127 + 'guest_checkout' => $guest_checkout,
128 + 'delayed_account_creation' => $delayed_account_creation,
129 + 'express_checkout' => 'null', // TODO: not solved yet.
130 + 'shipping_options_count' => 'null', // TODO: not solved yet.
131 + 'coupon_used' => $coupon_used,
132 + 'payment_options' => $enabled_payment_options,
125 133 );
126 134
127 135 return $shared_data;
128 136 }
@@ -163,8 +171,9 @@
163 171 }
164 172
165 173 $this->cart_checkout_templates_in_use = wp_is_block_theme()
166 174 && class_exists( '\Automattic\WooCommerce\Blocks\Package' )
175 + // @phan-suppress-current-line UnusedPluginSuppression @phan-suppress-next-line PhanUndeclaredClassMethod -- If the class exists (as of WooCommerce 8.5.0), the method exists. See also: https://github.com/phan/phan/issues/1204
167 176 && version_compare( \Automattic\WooCommerce\Blocks\Package::get_version(), '10.6.0', '>=' );
168 177
169 178 // Cart/Checkout *pages* are in use if the templates are not in use. Return their content and do nothing else.
170 179 if ( ! $this->cart_checkout_templates_in_use ) {
@@ -203,21 +212,8 @@
203 212 $cart_template = get_block_template( 'woocommerce/woocommerce//cart' );
204 213 }
205 214 }
206 215
207 - if ( function_exists( 'gutenberg_get_block_template' ) ) {
208 - $checkout_template = gutenberg_get_block_template( 'woocommerce/woocommerce//page-checkout' );
209 - $cart_template = gutenberg_get_block_template( 'woocommerce/woocommerce//page-cart' );
210 -
211 - if ( ! $checkout_template ) {
212 - $checkout_template = gutenberg_get_block_template( 'woocommerce/woocommerce//checkout' );
213 - }
214 -
215 - if ( ! $cart_template ) {
216 - $cart_template = gutenberg_get_block_template( 'woocommerce/woocommerce//cart' );
217 - }
218 - }
219 -
220 216 if ( ! empty( $checkout_template->content ) ) {
221 217 // Checkout template is in use, but we need to see if the page-content-wrapper is in use, or if the template is being used directly.
222 218 $this->checkout_content_source = $checkout_template->content;
223 219 $is_using_page_content = str_contains( $checkout_template->content, '<!-- wp:woocommerce/page-content-wrapper {"page":"checkout"}' );
@@ -257,59 +253,90 @@
257 253 );
258 254 }
259 255
260 256 /**
261 - * Default event properties which should be included with all events.
257 + * Request-scoped — not for page output, see `get_page_common_properties()`.
262 258 *
259 + * Default event properties for events the server fires itself on an uncached
260 + * request. See `WC_Analytics_Tracking::get_common_properties()`.
261 + *
263 262 * @return array Array of standard event props.
264 263 */
265 264 public function get_common_properties() {
266 - $site_info = array(
267 - 'blog_id' => Jetpack_Connection::get_site_id(),
268 - 'ui' => $this->get_user_id(),
269 - 'url' => home_url(),
270 - 'woo_version' => WC()->version,
271 - 'store_admin' => in_array( array( 'administrator', 'shop_manager' ), wp_get_current_user()->roles, true ) ? 1 : 0,
272 - 'device' => wp_is_mobile() ? 'mobile' : 'desktop',
273 - 'template_used' => $this->cart_checkout_templates_in_use ? '1' : '0',
274 - 'additional_blocks_on_cart_page' => $this->additional_blocks_on_cart_page,
275 - 'additional_blocks_on_checkout_page' => $this->additional_blocks_on_checkout_page,
276 - 'store_currency' => get_woocommerce_currency(),
265 + $common_properties = WC_Analytics_Tracking::get_common_properties();
266 + /**
267 + * Allow defining custom event properties in WooCommerce Analytics.
268 + *
269 + * See `WC_Analytics_Tracking::get_properties()` for the full contract
270 + * around `$is_client_supplied`.
271 + *
272 + * @module woocommerce-analytics
273 + *
274 + * @since 12.5
275 + * @since 0.18.0 Added the `$is_client_supplied` parameter. This call site
276 + * also began passing `$event_name`, which the hook already had.
277 + *
278 + * @param array $properties Array of event props to be filtered.
279 + * @param string $event_name Event name. Empty string here: this call builds
280 + * common properties, not properties for a specific event.
281 + * @param bool $is_client_supplied Whether the props came from an untrusted client.
282 + */
283 + $properties = apply_filters(
284 + 'jetpack_woocommerce_analytics_event_props',
285 + $common_properties,
286 + '',
287 + false
277 288 );
278 - $cart_checkout_info = $this->get_cart_checkout_info();
279 - return array_merge( $site_info, $cart_checkout_info );
289 +
290 + return $properties;
280 291 }
281 292
282 293 /**
283 - * Render tracks event properties as string of JavaScript object props.
294 + * Default event properties for the client, excluding anything derived from
295 + * the current request.
284 296 *
285 - * @param array $properties Array of key/value pairs.
286 - * @return string String of the form "key1: value1, key2: value2, " (etc).
297 + * Used for the properties embedded in front-end page HTML, which is cached
298 + * and replayed to later visitors. See
299 + * `WC_Analytics_Tracking::get_page_common_properties()`.
300 + *
301 + * @since 0.16.7
302 + *
303 + * @return array Array of standard event props.
287 304 */
288 - private function render_properties_as_js( $properties ) {
289 - $js_args_string = '';
290 - foreach ( $properties as $key => $value ) {
291 - if ( is_array( $value ) ) {
292 - $js_args_string = $js_args_string . "'$key': " . wp_json_encode( $value ) . ',';
293 - } else {
294 - $js_args_string = $js_args_string . "'$key': '" . esc_js( $value ) . "', ";
295 - }
296 - }
297 - return $js_args_string;
305 + public function get_page_common_properties() {
306 + $common_properties = WC_Analytics_Tracking::get_page_common_properties();
307 +
308 + /** This filter is documented in src/class-woo-analytics-trait.php */
309 + return apply_filters(
310 + 'jetpack_woocommerce_analytics_event_props',
311 + $common_properties,
312 + '',
313 + false
314 + );
298 315 }
299 316
300 317 /**
301 - * Record an event with optional product and custom properties.
318 + * Enqueue an event with optional product and custom properties.
302 319 *
303 - * @param string $event_name The name of the event to record.
304 - * @param array $properties Optional array of (key => value) event properties.
305 - * @param integer $product_id The id of the product relating to the event.
320 + * @param string $event_name The name of the event to record.
321 + * @param array $properties Optional array of (key => value) event properties.
322 + * @param integer|null $product_id The id of the product relating to the event.
306 323 *
307 - * @return string|void
324 + * @return void
308 325 */
309 - public function record_event( $event_name, $properties = array(), $product_id = null ) {
310 - $js = $this->process_event_properties( $event_name, $properties, $product_id );
311 - wc_enqueue_js( "_wca.push({$js});" );
326 + public function enqueue_event( $event_name, $properties = array(), $product_id = null ) {
327 + // Only set product details if we have a product id.
328 + if ( $product_id ) {
329 + $product = wc_get_product( $product_id );
330 + if ( ! $product instanceof WC_Product ) {
331 + return;
332 + }
333 + $product_details = $this->get_product_details( $product );
334 + }
335 +
336 + $event_properties = array_merge( $product_details ?? array(), $properties );
337 +
338 + WC_Analytics_Tracking::add_event_to_queue( $event_name, $event_properties );
312 339 }
313 340
314 341 /**
315 342 * Gather relevant product information
@@ -327,9 +354,9 @@
327 354 );
328 355 }
329 356
330 357 /**
331 - * Gets product categories or varation attributes as a formatted concatenated string
358 + * Gets product categories or variation attributes as a formatted concatenated string
332 359 *
333 360 * @param object $product WC_Product.
334 361 * @return string
335 362 */
@@ -344,9 +371,9 @@
344 371 $line = wc_get_formatted_variation( $variation_data, true );
345 372 } else {
346 373 $out = array();
347 374 $categories = get_the_terms( $product->get_id(), 'product_cat' );
348 - if ( $categories ) {
375 + if ( is_array( $categories ) ) {
349 376 foreach ( $categories as $category ) {
350 377 $out[] = $category->name;
351 378 }
352 379 }
@@ -355,59 +382,11 @@
355 382 return $line;
356 383 }
357 384
358 385 /**
359 - * Compose event properties.
360 - *
361 - * @param string $event_name The name of the event to record.
362 - * @param array $properties Optional array of (key => value) event properties.
363 - * @param integer $product_id Optional id of the product relating to the event.
364 - *
365 - * @return string|void
366 - */
367 - public function process_event_properties( $event_name, $properties = array(), $product_id = null ) {
368 -
369 - // Only set product details if we have a product id.
370 - if ( $product_id ) {
371 - $product = wc_get_product( $product_id );
372 - if ( ! $product instanceof WC_Product ) {
373 - return;
374 - }
375 - $product_details = $this->get_product_details( $product );
376 - }
377 -
378 - /**
379 - * Allow defining custom event properties in WooCommerce Analytics.
380 - *
381 - * @module woocommerce-analytics
382 - *
383 - * @since 12.5
384 - *
385 - * @param array $all_props Array of event props to be filtered.
386 - */
387 - $all_props = apply_filters(
388 - 'jetpack_woocommerce_analytics_event_props',
389 - array_merge(
390 - $this->get_common_properties(), // We put this here to allow override of common props.
391 - $properties
392 - )
393 - );
394 -
395 - $js = "{'_en': '" . esc_js( $event_name ) . "'";
396 -
397 - if ( isset( $product_details ) ) {
398 - $all_props = array_merge( $all_props, $product_details );
399 - }
400 -
401 - $js .= ',' . $this->render_properties_as_js( $all_props ) . '}';
402 -
403 - return $js;
404 - }
405 -
406 - /**
407 386 * Get the current user id
408 387 *
409 - * @return int
388 + * @return string|null
410 389 */
411 390 public function get_user_id() {
412 391 if ( is_user_logged_in() ) {
413 392 $blogid = Jetpack_Connection::get_site_id();
@@ -413,9 +392,9 @@
413 392 $blogid = Jetpack_Connection::get_site_id();
414 393 $userid = get_current_user_id();
415 394 return $blogid . ':' . $userid;
416 395 }
417 - return 'null';
396 + return null;
418 397 }
419 398
420 399 /**
421 400 * Gets the IDs of additional blocks on the Cart/Checkout pages or templates.
@@ -423,9 +402,8 @@
423 402 * @param string $cart_or_checkout Whether to get blocks on the cart or checkout page.
424 403 * @return array All inner blocks on the page.
425 404 */
426 405 public function get_additional_blocks_on_page( $cart_or_checkout = 'cart' ) {
427 -
428 406 $additional_blocks_on_page_transient_name = 'jetpack_woocommerce_analytics_additional_blocks_on_' . $cart_or_checkout . '_page';
429 407 $additional_blocks_on_page = get_transient( $additional_blocks_on_page_transient_name );
430 408
431 409 if ( false !== $additional_blocks_on_page ) {
@@ -437,56 +415,58 @@
437 415 if ( 'checkout' === $cart_or_checkout ) {
438 416 $content = $this->checkout_content_source;
439 417 }
440 418
441 - $parsed_blocks = parse_blocks( $content );
442 - $other_blocks = array_filter(
443 - $parsed_blocks,
444 - function ( $block ) use ( $cart_or_checkout ) {
445 - if ( ! isset( $block['blockName'] ) ) {
446 - return false;
447 - }
419 + $blocks_to_ignore = array(
420 + 'woocommerce/classic-shortcode',
421 + 'core/shortcode',
422 + 'checkout' === $cart_or_checkout ? 'woocommerce/checkout' : 'woocommerce/cart',
423 + );
448 424
449 - if ( 'woocommerce/classic-shortcode' === $block['blockName'] ) {
450 - return false;
451 - }
425 + $scanner = Block_Scanner::create( $content );
426 + if ( ! $scanner ) {
427 + return array();
428 + }
452 429
453 - if ( 'core/shortcode' === $block['blockName'] ) {
454 - return false;
455 - }
430 + $found_blocks = array();
431 + $ignored_block_depth = 0; // Count how many ignored blocks we're nested inside.
456 432
457 - if ( 'checkout' === $cart_or_checkout && 'woocommerce/checkout' !== $block['blockName'] ) {
458 - return true;
459 - }
433 + while ( $scanner->next_delimiter() ) {
434 + $type = $scanner->get_delimiter_type();
435 + $block_type = $scanner->get_block_type();
436 + $is_ignored_block = in_array( $block_type, $blocks_to_ignore, true );
460 437
461 - if ( 'cart' === $cart_or_checkout && 'woocommerce/cart' !== $block['blockName'] ) {
462 - return true;
463 - }
438 + switch ( $type ) {
439 + case Block_Scanner::OPENER:
440 + // If this is an ignored block, increase our nesting depth.
441 + if ( $is_ignored_block ) {
442 + ++$ignored_block_depth;
443 + }
464 444
465 - return false;
466 - }
467 - );
445 + // Only collect blocks that are not inside any ignored block.
446 + if ( 0 === $ignored_block_depth ) {
447 + $found_blocks[] = $block_type;
448 + }
449 + break;
468 450
469 - $all_inner_blocks = array();
451 + case Block_Scanner::CLOSER:
452 + // If this closes an ignored block, decrease our nesting depth.
453 + if ( $is_ignored_block && $ignored_block_depth > 0 ) {
454 + --$ignored_block_depth;
455 + }
456 + break;
470 457
471 - // Loop over each "block group". In templates the blocks are grouped up.
472 - foreach ( $other_blocks as $block ) {
473 -
474 - // This check is necessary because sometimes this is null when using templates.
475 - if ( ! empty( $block['blockName'] ) ) {
476 - $all_inner_blocks[] = $block['blockName'];
458 + case Block_Scanner::VOID:
459 + // Void blocks: only collect if we're not inside an ignored block and this isn't an ignored block itself.
460 + if ( 0 === $ignored_block_depth && ! $is_ignored_block ) {
461 + $found_blocks[] = $block_type;
462 + }
463 + break;
477 464 }
465 + }
478 466
479 - if ( ! isset( $block['innerBlocks'] ) || ! is_array( $block['innerBlocks'] ) || 0 === count( $block['innerBlocks'] ) ) {
480 - continue;
481 - }
482 -
483 - foreach ( $block['innerBlocks'] as $inner_content ) {
484 - $all_inner_blocks = array_merge( $all_inner_blocks, $this->get_inner_blocks( $inner_content ) );
485 - }
486 - }
487 - set_transient( $additional_blocks_on_page_transient_name, $all_inner_blocks, DAY_IN_SECONDS );
488 - return $all_inner_blocks;
467 + set_transient( $additional_blocks_on_page_transient_name, $found_blocks, DAY_IN_SECONDS );
468 + return $found_blocks;
489 469 }
490 470
491 471 /**
492 472 * Gets an array containing the block or shortcode use properties for the Cart page.
@@ -498,9 +478,9 @@
498 478
499 479 $content = $this->cart_content_source;
500 480 $block_presence = str_contains( $content, '<!-- wp:woocommerce/cart' );
501 481 $shortcode_presence = str_contains( $content, '[woocommerce_cart]' );
502 - $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode' );
482 + $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode {"shortcode":"cart"}' );
503 483
504 484 $new_info['cart_page_contains_cart_block'] = $block_presence ? '1' : '0';
505 485 $new_info['cart_page_contains_cart_shortcode'] = $shortcode_presence || $classic_shortcode_presence ? '1' : '0';
506 486 return $new_info;
@@ -516,9 +496,9 @@
516 496
517 497 $content = $this->checkout_content_source;
518 498 $block_presence = str_contains( $content, '<!-- wp:woocommerce/checkout' );
519 499 $shortcode_presence = str_contains( $content, '[woocommerce_checkout]' );
520 - $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode' );
500 + $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode {"shortcode":"checkout"}' );
521 501
522 502 $new_info['checkout_page_contains_checkout_block'] = $block_presence ? '1' : '0';
523 503 $new_info['checkout_page_contains_checkout_shortcode'] = $shortcode_presence || $classic_shortcode_presence ? '1' : '0';
524 504 return $new_info;
@@ -541,18 +521,18 @@
541 521 );
542 522 return $info;
543 523 }
544 524
545 - /**
546 - * Search a specific post for text content.
547 - *
548 - * Note: similar code is in a WooCommerce core PR:
549 - * https://github.com/woocommerce/woocommerce/pull/25932
550 - *
551 - * @param integer $post_id The id of the post to search.
552 - * @param string $text The text to search for.
553 - * @return integer 1 if post contains $text (otherwise 0).
554 - */
525 + /**
526 + * Search a specific post for text content.
527 + *
528 + * Note: similar code is in a WooCommerce core PR:
529 + * https://github.com/woocommerce/woocommerce/pull/25932
530 + *
531 + * @param integer $post_id The id of the post to search.
532 + * @param string $text The text to search for.
533 + * @return integer 1 if post contains $text (otherwise 0).
534 + */
555 535 public function post_contains_text( $post_id, $text ) {
556 536 global $wpdb;
557 537
558 538 // Search for the text anywhere in the post.
@@ -571,6 +551,193 @@
571 551 )
572 552 );
573 553
574 554 return ( '0' !== $result ) ? 1 : 0;
555 + }
556 +
557 + /**
558 + * Get the cart total
559 + *
560 + * @return float
561 + */
562 + public function get_cart_total() {
563 + $cart = WC()->cart;
564 + if ( $cart === null ) {
565 + return 0;
566 + }
567 + return $cart->get_total( 'tracking' );
568 + }
569 +
570 + /**
571 + * Get the cart subtotal
572 + *
573 + * @return float
574 + */
575 + public function get_cart_subtotal() {
576 + $cart = WC()->cart;
577 + if ( $cart === null ) {
578 + return 0;
579 + }
580 + return $cart->get_subtotal();
581 + }
582 +
583 + /**
584 + * Get the cart shipping total
585 + *
586 + * @return float
587 + */
588 + public function get_cart_shipping_total() {
589 + $cart = WC()->cart;
590 + if ( $cart === null ) {
591 + return 0;
592 + }
593 + return $cart->get_shipping_total();
594 + }
595 +
596 + /**
597 + * Get the cart taxes
598 + *
599 + * @return float
600 + */
601 + public function get_cart_taxes() {
602 + $cart = WC()->cart;
603 + if ( $cart === null ) {
604 + return 0;
605 + }
606 + return $cart->get_taxes_total();
607 + }
608 +
609 + /**
610 + * Get the cart discount total
611 + *
612 + * @return float
613 + */
614 + public function get_total_discounts() {
615 + $cart = WC()->cart;
616 + if ( $cart === null ) {
617 + return 0;
618 + }
619 + return $cart->get_discount_total();
620 + }
621 +
622 + /**
623 + * Get number of items in the cart
624 + *
625 + * @return int
626 + */
627 + public function get_cart_items_count() {
628 + $cart = WC()->cart;
629 + if ( $cart === null ) {
630 + return 0;
631 + }
632 + return $cart->get_cart_contents_count();
633 + }
634 +
635 + /**
636 + * Retrieves the breadcrumb trail as an array of page titles.
637 + *
638 + * This function attempts to generate a hierarchical breadcrumb trail for the current page or post.
639 + * - For the front page, it returns "Home".
640 + * - For WooCommerce product, category, or tag pages, it uses the WooCommerce breadcrumb generator and prepends the shop page title if needed.
641 + * - For regular pages, it builds the breadcrumb from the page's ancestors, ordered from top-level to current.
642 + * - For all other cases, it returns the current page's title.
643 + *
644 + * Titles are capped before being returned; see `cap_page_string()`.
645 + *
646 + * @return array The breadcrumb trail as an array of titles.
647 + */
648 + private function get_breadcrumb_titles() {
649 + return array_map( array( $this, 'cap_page_string' ), $this->build_breadcrumb_titles() );
650 + }
651 +
652 + /**
653 + * Limit the length of a caller-influenced string bound for the page output.
654 + *
655 + * Breadcrumb titles come from `post_title`, which core does not bound, so this
656 + * is their only limit. The search term is already blanked by
657 + * `WP_Query::parse_query()` above 1600 bytes; capping it further keeps the
658 + * search pixel URL, which carries the term twice, from being rejected outright.
659 + *
660 + * Truncated values keep an ellipsis so they stay distinguishable downstream
661 + * from a value that genuinely ended at the limit.
662 + *
663 + * @since 0.16.7
664 + *
665 + * @param string $value The value bound for the page output.
666 + * @return string The value, truncated if it exceeded the limit.
667 + */
668 + private function cap_page_string( $value ) {
669 + $max_length = 200;
670 +
671 + $value = (string) $value;
672 +
673 + if ( mb_strlen( $value ) <= $max_length ) {
674 + return $value;
675 + }
676 +
677 + return mb_substr( $value, 0, $max_length - 1 ) . '…';
678 + }
679 +
680 + /**
681 + * Build the uncapped breadcrumb trail. See `get_breadcrumb_titles()`.
682 + *
683 + * @return array The breadcrumb trail as an array of titles.
684 + */
685 + private function build_breadcrumb_titles() {
686 + if ( is_front_page() ) {
687 + return array( __( 'Home', 'woocommerce-analytics' ) );
688 + }
689 +
690 + if ( class_exists( '\WC_Breadcrumb' ) ) {
691 + $breadcrumb = new \WC_Breadcrumb();
692 + $crumbs = $breadcrumb->generate();
693 + $titles = wp_list_pluck( $crumbs, 0 );
694 +
695 + if ( is_product() || is_product_category() || is_product_tag() ) {
696 + $titles = $this->prepend_shop_page_title( $titles );
697 + }
698 +
699 + if ( ! empty( $titles ) ) {
700 + return $titles;
701 + }
702 + }
703 +
704 + // If it's a page, get the hierarchical title.
705 + if ( is_page() ) {
706 + $titles = array();
707 + $page_id = get_queried_object_id();
708 + $ancestors = get_post_ancestors( $page_id );
709 + // Reverse the ancestors to get the top-level first.
710 + $ancestors = array_reverse( $ancestors );
711 +
712 + foreach ( $ancestors as $ancestor ) {
713 + $titles[] = get_the_title( $ancestor );
714 + }
715 + $titles[] = get_the_title( $page_id );
716 +
717 + return $titles;
718 + }
719 +
720 + return array( get_the_title() );
721 + }
722 +
723 + /**
724 + * Prepend the shop page title if it's not already present.
725 + *
726 + * @param array $titles The titles to prepend the shop page title to.
727 + * @return array The titles with the shop page title prepended.
728 + */
729 + private function prepend_shop_page_title( array $titles ) {
730 + $shop_page_id = wc_get_page_id( 'shop' );
731 + if ( ! $shop_page_id ) {
732 + return $titles;
733 + }
734 +
735 + $shop_page_title = get_the_title( $shop_page_id );
736 +
737 + if ( ! $shop_page_title || ( ! empty( $titles ) && $titles[0] === $shop_page_title ) ) {
738 + return $titles;
739 + }
740 +
741 + return array_merge( array( $shop_page_title ), $titles );
575 742 }
576 743 }