PluginProbe
WooCommerce / 11.1.0-rc.2
WooCommerce v11.1.0-rc.2
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 All 648 releases
woocommerce / src / Blocks / CoreBreadcrumbsCompatibility.php
CoreBreadcrumbsCompatibility.php
624 lines 17.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 declare(strict_types=1);
3
4 namespace Automattic\WooCommerce\Blocks;
5
6 /**
7 * Adds WooCommerce compatibility behavior to the Core Breadcrumbs block.
8 *
9 * @internal
10 */
11 final class CoreBreadcrumbsCompatibility {
12 /*
13 * Compatibility methods.
14 */
15
16 /**
17 * Set the preferred taxonomy and term for the breadcrumbs block on the product post type.
18 *
19 * This method mimics the behavior of WC_Breadcrumb::add_crumbs_single() to ensure
20 * consistent breadcrumb term selection between WooCommerce's legacy breadcrumbs
21 * and the Core breadcrumbs block.
22 *
23 * @internal
24 *
25 * @param array $settings The settings for the breadcrumbs block.
26 * @param string $post_type The post type.
27 * @param int $post_id The current post ID.
28 * @return array The settings for the breadcrumbs block.
29 */
30 public function set_product_breadcrumbs_preferred_taxonomy( $settings, $post_type, $post_id = 0 ) {
31 if ( ! is_array( $settings ) || 'product' !== $post_type ) {
32 return $settings;
33 }
34
35 $settings['taxonomy'] = 'product_cat';
36 $post_id = (int) $post_id;
37
38 if ( ! $post_id ) {
39 return $settings;
40 }
41
42 $terms = wc_get_product_terms(
43 $post_id,
44 'product_cat',
45 /**
46 * Filters the arguments used to fetch product terms for breadcrumbs.
47 *
48 * @since 9.5.0
49 *
50 * @param array $args Array of arguments for `wc_get_product_terms()`.
51 */
52 apply_filters(
53 'woocommerce_breadcrumb_product_terms_args',
54 array(
55 'orderby' => 'parent',
56 'order' => 'DESC',
57 )
58 )
59 );
60
61 if ( empty( $terms ) || is_wp_error( $terms ) ) {
62 return $settings;
63 }
64
65 /**
66 * Filters the main term used in product breadcrumbs.
67 *
68 * @since 9.5.0
69 *
70 * @param \WP_Term $main_term The main term to be used in breadcrumbs.
71 * @param \WP_Term[] $terms Array of all product category terms.
72 */
73 $main_term = apply_filters( 'woocommerce_breadcrumb_main_term', $terms[0], $terms );
74
75 if ( $main_term instanceof \WP_Term ) {
76 $settings['term'] = $main_term->slug;
77 }
78
79 return $settings;
80 }
81
82 /**
83 * Apply WooCommerce breadcrumb filters to Core breadcrumbs block items.
84 *
85 * This bridges the Core breadcrumbs block with WooCommerce's legacy breadcrumb filters,
86 * ensuring backward compatibility for sites that have customized breadcrumbs using
87 * the `woocommerce_get_breadcrumb` filter.
88 *
89 * @internal
90 *
91 * @param array $items Array of breadcrumb items from Core.
92 * @return array Modified breadcrumb items.
93 */
94 public function apply_woocommerce_breadcrumb_filters( $items ) {
95 if ( ! is_array( $items ) ) {
96 return $items;
97 }
98
99 $items = $this->apply_woocommerce_core_breadcrumb_adjustments( $items );
100
101 if ( ! has_filter( 'woocommerce_get_breadcrumb' ) ) {
102 return $items;
103 }
104
105 // Convert Core format to WooCommerce format.
106 // Core: array( 'url' => '...', 'label' => '...' )
107 // Woo: array( 'label', 'url' ).
108 $wc_crumbs = array_map(
109 function ( $item ) {
110 return array(
111 $item['label'] ?? '',
112 $item['url'] ?? '',
113 );
114 },
115 $items
116 );
117
118 /**
119 * Filters the breadcrumb trail array.
120 *
121 * @since 2.3.0
122 *
123 * @param array $crumbs The breadcrumb trail.
124 * @param \WC_Breadcrumb|null $breadcrumb The breadcrumb object (null when called from Core block).
125 */
126 $wc_crumbs = apply_filters( 'woocommerce_get_breadcrumb', $wc_crumbs, null );
127
128 $core_items = array();
129
130 foreach ( $wc_crumbs as $index => $crumb ) {
131 $item = isset( $items[ $index ] ) && is_array( $items[ $index ] ) ? $items[ $index ] : array();
132 $label = is_array( $crumb ) ? ( $crumb[0] ?? '' ) : '';
133 $url = is_array( $crumb ) ? ( $crumb[1] ?? '' ) : '';
134
135 $item['label'] = $label;
136
137 if ( $url ) {
138 $item['url'] = $url;
139 } else {
140 unset( $item['url'] );
141 }
142
143 $core_items[] = $item;
144 }
145
146 return $core_items;
147 }
148
149 /**
150 * Apply WooCommerce breadcrumb behavior to Core breadcrumbs.
151 *
152 * @param array $items Array of breadcrumb items from Core.
153 * @return array Modified breadcrumb items.
154 */
155 private function apply_woocommerce_core_breadcrumb_adjustments( $items ) {
156 if ( ! is_array( $items ) ) {
157 return $items;
158 }
159
160 $items = $this->replace_product_archive_breadcrumb_label( $items );
161 $items = $this->prepend_shop_page_to_product_taxonomy_breadcrumbs( $items );
162 $items = $this->prepend_taxonomy_label_to_product_taxonomy_breadcrumbs( $items );
163 $items = $this->prepend_shop_page_to_product_search_breadcrumbs( $items );
164 $items = $this->replace_product_tag_breadcrumb_label( $items );
165 $items = $this->replace_search_breadcrumb_label( $items );
166 $items = $this->prepend_my_account_page_to_endpoint_breadcrumbs( $items );
167 $items = $this->apply_home_breadcrumb_url_filter( $items );
168
169 return $items;
170 }
171
172 /**
173 * Apply WooCommerce's Home breadcrumb URL filter.
174 *
175 * @param array $items Array of breadcrumb items from Core.
176 * @return array Modified breadcrumb items.
177 */
178 private function apply_home_breadcrumb_url_filter( $items ) {
179 if ( ! has_filter( 'woocommerce_breadcrumb_home_url' ) ) {
180 return $items;
181 }
182
183 $home_index = $this->get_breadcrumb_item_index_by_url( $items, home_url( '/' ) );
184
185 if ( null === $home_index ) {
186 return $items;
187 }
188
189 $default_home_url = home_url();
190
191 /**
192 * Filters the Home breadcrumb URL.
193 *
194 * @param string $url The Home breadcrumb URL.
195 *
196 * @since 2.3.0
197 */
198 $home_url = apply_filters( 'woocommerce_breadcrumb_home_url', $default_home_url );
199
200 $items[ $home_index ]['url'] = is_string( $home_url ) ? $home_url : $default_home_url;
201
202 return $items;
203 }
204
205 /**
206 * Replace product archive breadcrumb labels with the WooCommerce shop page title.
207 *
208 * @param array $items Array of breadcrumb items from Core.
209 * @return array Modified breadcrumb items.
210 */
211 private function replace_product_archive_breadcrumb_label( $items ) {
212 $shop_url = get_post_type_archive_link( 'product' );
213
214 if ( ! $shop_url || null === $this->get_breadcrumb_item_index_by_url( $items, $shop_url ) ) {
215 return $items;
216 }
217
218 $shop_page_item = $this->get_shop_page_breadcrumb_item( $shop_url );
219
220 if ( empty( $shop_page_item ) ) {
221 return $items;
222 }
223
224 foreach ( $items as $index => $item ) {
225 if ( self::are_breadcrumb_urls_equal( $item['url'] ?? '', $shop_page_item['url'] ) ) {
226 $items[ $index ]['label'] = $shop_page_item['label'];
227 }
228 }
229
230 return $items;
231 }
232
233 /**
234 * Prepend the shop page to product taxonomy breadcrumbs.
235 *
236 * @param array $items Array of breadcrumb items from Core.
237 * @return array Modified breadcrumb items.
238 */
239 private function prepend_shop_page_to_product_taxonomy_breadcrumbs( $items ) {
240 if ( ! ( is_product_category() || is_product_tag() ) ) {
241 return $items;
242 }
243
244 $permalinks = wc_get_permalink_structure();
245 $shop_page = $this->get_woocommerce_page_post( 'shop' );
246
247 if (
248 ! $shop_page ||
249 ! isset( $permalinks['product_base'] ) ||
250 ! strstr( $permalinks['product_base'], '/' . $shop_page->post_name ) ||
251 intval( get_option( 'page_on_front' ) ) === $shop_page->ID
252 ) {
253 return $items;
254 }
255
256 return $this->prepend_shop_page_to_breadcrumbs( $items );
257 }
258
259 /**
260 * Prepend taxonomy labels to product taxonomy breadcrumbs.
261 *
262 * @param array $items Array of breadcrumb items from Core.
263 * @return array Modified breadcrumb items.
264 */
265 private function prepend_taxonomy_label_to_product_taxonomy_breadcrumbs( $items ) {
266 if ( ! is_product_taxonomy() || empty( $items ) ) {
267 return $items;
268 }
269
270 $current_term = $this->get_queried_term();
271
272 if ( ! $current_term || in_array( $current_term->taxonomy, array( 'product_brand', 'product_cat', 'product_tag' ), true ) ) {
273 return $items;
274 }
275
276 $taxonomy = get_taxonomy( $current_term->taxonomy );
277
278 if ( ! $taxonomy || ! $taxonomy->labels->name ) {
279 return $items;
280 }
281
282 $insert_index = $this->get_first_breadcrumb_insert_index( $items );
283
284 if ( isset( $items[ $insert_index ]['label'] ) && $taxonomy->labels->name === $items[ $insert_index ]['label'] ) {
285 return $items;
286 }
287
288 return $this->insert_parent_breadcrumb_item(
289 $items,
290 array(
291 'label' => $taxonomy->labels->name,
292 )
293 );
294 }
295
296 /**
297 * Prepend the shop page to product search breadcrumbs.
298 *
299 * @param array $items Array of breadcrumb items from Core.
300 * @return array Modified breadcrumb items.
301 */
302 private function prepend_shop_page_to_product_search_breadcrumbs( $items ) {
303 if ( ! $this->is_product_search() || intval( get_option( 'page_on_front' ) ) === wc_get_page_id( 'shop' ) ) {
304 return $items;
305 }
306
307 return $this->prepend_shop_page_to_breadcrumbs( $items );
308 }
309
310 /**
311 * Replace Core's search breadcrumb label with WooCommerce's search label.
312 *
313 * @param array $items Array of breadcrumb items from Core.
314 * @return array Modified breadcrumb items.
315 */
316 private function replace_search_breadcrumb_label( $items ) {
317 if ( ! is_search() || empty( $items ) ) {
318 return $items;
319 }
320
321 $search_url = (int) get_query_var( 'paged' ) > 1 ? get_pagenum_link( 1 ) : '';
322
323 /* translators: %s: search term */
324 return $this->replace_current_archive_breadcrumb_label( $items, sprintf( __( 'Search results for &ldquo;%s&rdquo;', 'woocommerce' ), get_search_query() ), $search_url );
325 }
326
327 /**
328 * Replace product tag breadcrumb labels with WooCommerce's tag archive label.
329 *
330 * @param array $items Array of breadcrumb items from Core.
331 * @return array Modified breadcrumb items.
332 */
333 private function replace_product_tag_breadcrumb_label( $items ) {
334 if ( ! is_product_tag() || empty( $items ) ) {
335 return $items;
336 }
337
338 $current_term = $this->get_queried_term();
339
340 if ( ! $current_term ) {
341 return $items;
342 }
343
344 $tag_link = get_term_link( $current_term, 'product_tag' );
345
346 if ( is_wp_error( $tag_link ) ) {
347 $tag_link = '';
348 }
349
350 /* translators: %s: product tag */
351 return $this->replace_current_archive_breadcrumb_label( $items, sprintf( __( 'Products tagged &ldquo;%s&rdquo;', 'woocommerce' ), $current_term->name ), $tag_link );
352 }
353
354 /**
355 * Prepend the My Account page to account endpoint breadcrumbs.
356 *
357 * @param array $items Array of breadcrumb items from Core.
358 * @return array Modified breadcrumb items.
359 */
360 private function prepend_my_account_page_to_endpoint_breadcrumbs( $items ) {
361 if ( ! is_wc_endpoint_url() || ! is_account_page() ) {
362 return $items;
363 }
364
365 $my_account_page = $this->get_woocommerce_page_post( 'myaccount' );
366 $my_account_url = $my_account_page ? get_permalink( $my_account_page ) : '';
367
368 if ( ! $my_account_page || ! $my_account_url ) {
369 return $items;
370 }
371
372 $woocommerce = WC();
373
374 if ( $woocommerce->query instanceof \WC_Query ) {
375 $endpoint = $woocommerce->query->get_current_endpoint();
376 $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Used only to select the breadcrumb label.
377 $endpoint_title = $endpoint ? $woocommerce->query->get_endpoint_title( $endpoint, $action ) : '';
378
379 if ( $endpoint_title ) {
380 $items = $this->replace_current_archive_breadcrumb_label( $items, $endpoint_title );
381 }
382 }
383
384 return $this->insert_parent_breadcrumb_item_if_missing_url(
385 $items,
386 array(
387 'label' => $my_account_page->post_title,
388 'url' => $my_account_url,
389 )
390 );
391 }
392
393 /*
394 * Utility methods.
395 */
396
397 /**
398 * Check whether the current request is a product search.
399 *
400 * @return bool Whether the current request is a product search.
401 */
402 private function is_product_search(): bool {
403 if ( ! is_search() ) {
404 return false;
405 }
406
407 $post_type = get_query_var( 'post_type' );
408
409 if ( is_array( $post_type ) ) {
410 return in_array( 'product', $post_type, true );
411 }
412
413 return 'product' === $post_type || is_shop();
414 }
415
416 /**
417 * Prepend the shop page to breadcrumb items.
418 *
419 * @param array $items Array of breadcrumb items from Core.
420 * @return array Modified breadcrumb items.
421 */
422 private function prepend_shop_page_to_breadcrumbs( $items ) {
423 $shop_page_item = $this->get_shop_page_breadcrumb_item();
424
425 if ( empty( $shop_page_item ) ) {
426 return $items;
427 }
428
429 $shop_page_index = $this->get_breadcrumb_item_index_by_url( $items, $shop_page_item['url'] );
430
431 if ( null !== $shop_page_index ) {
432 return $this->replace_breadcrumb_label_at_index( $items, $shop_page_index, $shop_page_item['label'] );
433 }
434
435 return $this->insert_parent_breadcrumb_item_if_missing_url( $items, $shop_page_item );
436 }
437
438 /**
439 * Get the shop page breadcrumb item.
440 *
441 * @param string $shop_url Shop archive URL.
442 * @return array|null Shop page breadcrumb item.
443 */
444 private function get_shop_page_breadcrumb_item( $shop_url = '' ) {
445 $shop_page = $this->get_woocommerce_page_post( 'shop' );
446 $shop_url = $shop_url ? $shop_url : (
447 $shop_page ? get_permalink( $shop_page ) : get_post_type_archive_link( 'product' )
448 );
449 $shop_label = $shop_page ? get_the_title( $shop_page ) : '';
450
451 if ( ! $shop_label ) {
452 $product_post_type = get_post_type_object( 'product' );
453 $shop_label = $product_post_type ? $product_post_type->labels->name : '';
454 }
455
456 if ( ! $shop_url || ! $shop_label ) {
457 return null;
458 }
459
460 return array(
461 'label' => $shop_label,
462 'url' => $shop_url,
463 );
464 }
465
466 /**
467 * Get a WooCommerce page post.
468 *
469 * @param string $page_name WooCommerce page name.
470 * @return \WP_Post|null WooCommerce page post.
471 */
472 private function get_woocommerce_page_post( string $page_name ) {
473 $page_id = wc_get_page_id( $page_name );
474
475 return $page_id > 0 ? get_post( $page_id ) : null;
476 }
477
478 /**
479 * Get the queried term.
480 *
481 * @return \WP_Term|null Queried term.
482 */
483 private function get_queried_term() {
484 $queried_object = $GLOBALS['wp_query']->get_queried_object();
485
486 return $queried_object instanceof \WP_Term ? $queried_object : null;
487 }
488
489 /**
490 * Replace the current archive breadcrumb label.
491 *
492 * @param array $items Array of breadcrumb items from Core.
493 * @param string $label Replacement label.
494 * @param string $archive_url Archive URL.
495 * @return array Modified breadcrumb items.
496 */
497 private function replace_current_archive_breadcrumb_label( $items, $label, $archive_url = '' ) {
498 $item_index = $this->get_breadcrumb_item_index_by_url( $items, $archive_url );
499
500 if ( null === $item_index ) {
501 $item_keys = array_keys( $items );
502
503 if ( empty( $item_keys ) ) {
504 return $items;
505 }
506
507 $item_index = end( $item_keys );
508 $paged = (int) get_query_var( 'paged' );
509
510 if ( $paged > 1 && count( $item_keys ) > 1 && isset( $items[ $item_index ]['label'] ) ) {
511 $pagination_label = sprintf(
512 /* translators: %s: page number */
513 __( 'Page %s', 'default' ), // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Match the Core Breadcrumbs block pagination label.
514 number_format_i18n( $paged )
515 );
516
517 if ( $pagination_label === (string) $items[ $item_index ]['label'] ) {
518 $item_index = $item_keys[ count( $item_keys ) - 2 ];
519 }
520 }
521
522 if ( ! empty( $items[ $item_index ]['url'] ) ) {
523 return $items;
524 }
525 }
526
527 return $this->replace_breadcrumb_label_at_index( $items, $item_index, $label );
528 }
529
530 /**
531 * Replace a breadcrumb label at an index.
532 *
533 * @param array $items Array of breadcrumb items from Core.
534 * @param int|string|null $index Breadcrumb item index.
535 * @param string $label Replacement label.
536 * @return array Modified breadcrumb items.
537 */
538 private function replace_breadcrumb_label_at_index( $items, $index, $label ) {
539 if ( null === $index || ! isset( $items[ $index ] ) ) {
540 return $items;
541 }
542
543 $items[ $index ]['label'] = $label;
544
545 return $items;
546 }
547
548 /**
549 * Insert a parent breadcrumb item if its URL is not already present.
550 *
551 * @param array $items Array of breadcrumb items from Core.
552 * @param array $item Breadcrumb item to insert.
553 * @return array Modified breadcrumb items.
554 */
555 private function insert_parent_breadcrumb_item_if_missing_url( $items, $item ) {
556 if ( empty( $item['url'] ) || null !== $this->get_breadcrumb_item_index_by_url( $items, $item['url'] ) ) {
557 return $items;
558 }
559
560 return $this->insert_parent_breadcrumb_item( $items, $item );
561 }
562
563 /**
564 * Insert a parent breadcrumb item after the home item.
565 *
566 * @param array $items Array of breadcrumb items from Core.
567 * @param array $item Breadcrumb item to insert.
568 * @return array Modified breadcrumb items.
569 */
570 private function insert_parent_breadcrumb_item( $items, $item ) {
571 array_splice( $items, $this->get_first_breadcrumb_insert_index( $items ), 0, array( $item ) );
572
573 return $items;
574 }
575
576 /**
577 * Get the first breadcrumb item index matching a URL.
578 *
579 * @param array $items Array of breadcrumb items from Core.
580 * @param string $url URL to find.
581 * @return int|string|null Breadcrumb item index.
582 */
583 private function get_breadcrumb_item_index_by_url( $items, $url ) {
584 if ( ! $url ) {
585 return null;
586 }
587
588 foreach ( $items as $index => $item ) {
589 if ( self::are_breadcrumb_urls_equal( $item['url'] ?? '', $url ) ) {
590 return $index;
591 }
592 }
593
594 return null;
595 }
596
597 /**
598 * Get the index where WooCommerce should insert parent breadcrumb items.
599 *
600 * @param array $items Array of breadcrumb items from Core.
601 * @return int Breadcrumb insertion index.
602 */
603 private function get_first_breadcrumb_insert_index( $items ) {
604 if ( empty( $items ) ) {
605 return 0;
606 }
607
608 $first_item = reset( $items );
609
610 return self::are_breadcrumb_urls_equal( $first_item['url'] ?? '', home_url( '/' ) ) ? 1 : 0;
611 }
612
613 /**
614 * Check whether two breadcrumb URLs are equivalent.
615 *
616 * @param string $first_url First URL.
617 * @param string $second_url Second URL.
618 * @return bool Whether the URLs are equivalent.
619 */
620 private static function are_breadcrumb_urls_equal( $first_url, $second_url ) {
621 return untrailingslashit( (string) $first_url ) === untrailingslashit( (string) $second_url );
622 }
623 }
624