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 -152 13.6.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 ) {
@@ -244,59 +253,90 @@
244 253 );
245 254 }
246 255
247 256 /**
248 - * Default event properties which should be included with all events.
257 + * Request-scoped — not for page output, see `get_page_common_properties()`.
249 258 *
259 + * Default event properties for events the server fires itself on an uncached
260 + * request. See `WC_Analytics_Tracking::get_common_properties()`.
261 + *
250 262 * @return array Array of standard event props.
251 263 */
252 264 public function get_common_properties() {
253 - $site_info = array(
254 - 'blog_id' => Jetpack_Connection::get_site_id(),
255 - 'ui' => $this->get_user_id(),
256 - 'url' => home_url(),
257 - 'woo_version' => WC()->version,
258 - 'store_admin' => in_array( array( 'administrator', 'shop_manager' ), wp_get_current_user()->roles, true ) ? 1 : 0,
259 - 'device' => wp_is_mobile() ? 'mobile' : 'desktop',
260 - 'template_used' => $this->cart_checkout_templates_in_use ? '1' : '0',
261 - 'additional_blocks_on_cart_page' => $this->additional_blocks_on_cart_page,
262 - 'additional_blocks_on_checkout_page' => $this->additional_blocks_on_checkout_page,
263 - '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
264 288 );
265 - $cart_checkout_info = $this->get_cart_checkout_info();
266 - return array_merge( $site_info, $cart_checkout_info );
289 +
290 + return $properties;
267 291 }
268 292
269 293 /**
270 - * 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.
271 296 *
272 - * @param array $properties Array of key/value pairs.
273 - * @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.
274 304 */
275 - private function render_properties_as_js( $properties ) {
276 - $js_args_string = '';
277 - foreach ( $properties as $key => $value ) {
278 - if ( is_array( $value ) ) {
279 - $js_args_string = $js_args_string . "'$key': " . wp_json_encode( $value ) . ',';
280 - } else {
281 - $js_args_string = $js_args_string . "'$key': '" . esc_js( $value ) . "', ";
282 - }
283 - }
284 - 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 + );
285 315 }
286 316
287 317 /**
288 - * Record an event with optional product and custom properties.
318 + * Enqueue an event with optional product and custom properties.
289 319 *
290 - * @param string $event_name The name of the event to record.
291 - * @param array $properties Optional array of (key => value) event properties.
292 - * @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.
293 323 *
294 - * @return string|void
324 + * @return void
295 325 */
296 - public function record_event( $event_name, $properties = array(), $product_id = null ) {
297 - $js = $this->process_event_properties( $event_name, $properties, $product_id );
298 - 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 );
299 339 }
300 340
301 341 /**
302 342 * Gather relevant product information
@@ -314,9 +354,9 @@
314 354 );
315 355 }
316 356
317 357 /**
318 - * Gets product categories or varation attributes as a formatted concatenated string
358 + * Gets product categories or variation attributes as a formatted concatenated string
319 359 *
320 360 * @param object $product WC_Product.
321 361 * @return string
322 362 */
@@ -331,9 +371,9 @@
331 371 $line = wc_get_formatted_variation( $variation_data, true );
332 372 } else {
333 373 $out = array();
334 374 $categories = get_the_terms( $product->get_id(), 'product_cat' );
335 - if ( $categories ) {
375 + if ( is_array( $categories ) ) {
336 376 foreach ( $categories as $category ) {
337 377 $out[] = $category->name;
338 378 }
339 379 }
@@ -342,59 +382,11 @@
342 382 return $line;
343 383 }
344 384
345 385 /**
346 - * Compose event properties.
347 - *
348 - * @param string $event_name The name of the event to record.
349 - * @param array $properties Optional array of (key => value) event properties.
350 - * @param integer $product_id Optional id of the product relating to the event.
351 - *
352 - * @return string|void
353 - */
354 - public function process_event_properties( $event_name, $properties = array(), $product_id = null ) {
355 -
356 - // Only set product details if we have a product id.
357 - if ( $product_id ) {
358 - $product = wc_get_product( $product_id );
359 - if ( ! $product instanceof WC_Product ) {
360 - return;
361 - }
362 - $product_details = $this->get_product_details( $product );
363 - }
364 -
365 - /**
366 - * Allow defining custom event properties in WooCommerce Analytics.
367 - *
368 - * @module woocommerce-analytics
369 - *
370 - * @since 12.5
371 - *
372 - * @param array $all_props Array of event props to be filtered.
373 - */
374 - $all_props = apply_filters(
375 - 'jetpack_woocommerce_analytics_event_props',
376 - array_merge(
377 - $this->get_common_properties(), // We put this here to allow override of common props.
378 - $properties
379 - )
380 - );
381 -
382 - $js = "{'_en': '" . esc_js( $event_name ) . "'";
383 -
384 - if ( isset( $product_details ) ) {
385 - $all_props = array_merge( $all_props, $product_details );
386 - }
387 -
388 - $js .= ',' . $this->render_properties_as_js( $all_props ) . '}';
389 -
390 - return $js;
391 - }
392 -
393 - /**
394 386 * Get the current user id
395 387 *
396 - * @return int
388 + * @return string|null
397 389 */
398 390 public function get_user_id() {
399 391 if ( is_user_logged_in() ) {
400 392 $blogid = Jetpack_Connection::get_site_id();
@@ -400,9 +392,9 @@
400 392 $blogid = Jetpack_Connection::get_site_id();
401 393 $userid = get_current_user_id();
402 394 return $blogid . ':' . $userid;
403 395 }
404 - return 'null';
396 + return null;
405 397 }
406 398
407 399 /**
408 400 * Gets the IDs of additional blocks on the Cart/Checkout pages or templates.
@@ -410,9 +402,8 @@
410 402 * @param string $cart_or_checkout Whether to get blocks on the cart or checkout page.
411 403 * @return array All inner blocks on the page.
412 404 */
413 405 public function get_additional_blocks_on_page( $cart_or_checkout = 'cart' ) {
414 -
415 406 $additional_blocks_on_page_transient_name = 'jetpack_woocommerce_analytics_additional_blocks_on_' . $cart_or_checkout . '_page';
416 407 $additional_blocks_on_page = get_transient( $additional_blocks_on_page_transient_name );
417 408
418 409 if ( false !== $additional_blocks_on_page ) {
@@ -424,56 +415,58 @@
424 415 if ( 'checkout' === $cart_or_checkout ) {
425 416 $content = $this->checkout_content_source;
426 417 }
427 418
428 - $parsed_blocks = parse_blocks( $content );
429 - $other_blocks = array_filter(
430 - $parsed_blocks,
431 - function ( $block ) use ( $cart_or_checkout ) {
432 - if ( ! isset( $block['blockName'] ) ) {
433 - return false;
434 - }
419 + $blocks_to_ignore = array(
420 + 'woocommerce/classic-shortcode',
421 + 'core/shortcode',
422 + 'checkout' === $cart_or_checkout ? 'woocommerce/checkout' : 'woocommerce/cart',
423 + );
435 424
436 - if ( 'woocommerce/classic-shortcode' === $block['blockName'] ) {
437 - return false;
438 - }
425 + $scanner = Block_Scanner::create( $content );
426 + if ( ! $scanner ) {
427 + return array();
428 + }
439 429
440 - if ( 'core/shortcode' === $block['blockName'] ) {
441 - return false;
442 - }
430 + $found_blocks = array();
431 + $ignored_block_depth = 0; // Count how many ignored blocks we're nested inside.
443 432
444 - if ( 'checkout' === $cart_or_checkout && 'woocommerce/checkout' !== $block['blockName'] ) {
445 - return true;
446 - }
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 );
447 437
448 - if ( 'cart' === $cart_or_checkout && 'woocommerce/cart' !== $block['blockName'] ) {
449 - return true;
450 - }
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 + }
451 444
452 - return false;
453 - }
454 - );
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;
455 450
456 - $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;
457 457
458 - // Loop over each "block group". In templates the blocks are grouped up.
459 - foreach ( $other_blocks as $block ) {
460 -
461 - // This check is necessary because sometimes this is null when using templates.
462 - if ( ! empty( $block['blockName'] ) ) {
463 - $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;
464 464 }
465 + }
465 466
466 - if ( ! isset( $block['innerBlocks'] ) || ! is_array( $block['innerBlocks'] ) || 0 === count( $block['innerBlocks'] ) ) {
467 - continue;
468 - }
469 -
470 - foreach ( $block['innerBlocks'] as $inner_content ) {
471 - $all_inner_blocks = array_merge( $all_inner_blocks, $this->get_inner_blocks( $inner_content ) );
472 - }
473 - }
474 - set_transient( $additional_blocks_on_page_transient_name, $all_inner_blocks, DAY_IN_SECONDS );
475 - return $all_inner_blocks;
467 + set_transient( $additional_blocks_on_page_transient_name, $found_blocks, DAY_IN_SECONDS );
468 + return $found_blocks;
476 469 }
477 470
478 471 /**
479 472 * Gets an array containing the block or shortcode use properties for the Cart page.
@@ -485,9 +478,9 @@
485 478
486 479 $content = $this->cart_content_source;
487 480 $block_presence = str_contains( $content, '<!-- wp:woocommerce/cart' );
488 481 $shortcode_presence = str_contains( $content, '[woocommerce_cart]' );
489 - $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode' );
482 + $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode {"shortcode":"cart"}' );
490 483
491 484 $new_info['cart_page_contains_cart_block'] = $block_presence ? '1' : '0';
492 485 $new_info['cart_page_contains_cart_shortcode'] = $shortcode_presence || $classic_shortcode_presence ? '1' : '0';
493 486 return $new_info;
@@ -503,9 +496,9 @@
503 496
504 497 $content = $this->checkout_content_source;
505 498 $block_presence = str_contains( $content, '<!-- wp:woocommerce/checkout' );
506 499 $shortcode_presence = str_contains( $content, '[woocommerce_checkout]' );
507 - $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode' );
500 + $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode {"shortcode":"checkout"}' );
508 501
509 502 $new_info['checkout_page_contains_checkout_block'] = $block_presence ? '1' : '0';
510 503 $new_info['checkout_page_contains_checkout_shortcode'] = $shortcode_presence || $classic_shortcode_presence ? '1' : '0';
511 504 return $new_info;
@@ -528,18 +521,18 @@
528 521 );
529 522 return $info;
530 523 }
531 524
532 - /**
533 - * Search a specific post for text content.
534 - *
535 - * Note: similar code is in a WooCommerce core PR:
536 - * https://github.com/woocommerce/woocommerce/pull/25932
537 - *
538 - * @param integer $post_id The id of the post to search.
539 - * @param string $text The text to search for.
540 - * @return integer 1 if post contains $text (otherwise 0).
541 - */
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 + */
542 535 public function post_contains_text( $post_id, $text ) {
543 536 global $wpdb;
544 537
545 538 // Search for the text anywhere in the post.
@@ -558,6 +551,193 @@
558 551 )
559 552 );
560 553
561 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 );
562 742 }
563 743 }