PluginProbe
Gutenberg / 22.9.0
Gutenberg v22.9.0
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / build / scripts / block-library / breadcrumbs.php

breadcrumbs.php in Gutenberg 22.9.0, at build/scripts/block-library/breadcrumbs.php

612 lines 19.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server-side rendering of the `core/breadcrumbs` block.
4 *
5 * @package WordPress
6 */
7
8 /**
9 * Renders the `core/breadcrumbs` block on the server.
10 *
11 * @since 7.0.0
12 *
13 * @param array $attributes Block attributes.
14 * @param string $content Block default content.
15 * @param WP_Block $block Block instance.
16 *
17 * @return string Returns the post breadcrumb for hierarchical post types.
18 */
19 function gutenberg_render_block_core_breadcrumbs( $attributes, $content, $block ) {
20 $is_front_page = is_front_page();
21
22 if ( ! $attributes['showOnHomePage'] && $is_front_page ) {
23 return '';
24 }
25
26 $is_home = is_home();
27 $page_for_posts = get_option( 'page_for_posts' );
28 $breadcrumb_items = array();
29
30 if ( $attributes['showHomeItem'] ) {
31 // We make `home` a link if not on front page, or if front page
32 // is set to a custom page and is paged.
33 if ( ! $is_front_page || ( 'page' === get_option( 'show_on_front' ) && (int) get_query_var( 'page' ) > 1 ) ) {
34 $breadcrumb_items[] = array(
35 'label' => __( 'Home' ),
36 'url' => home_url( '/' ),
37 );
38 } else {
39 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item( __( 'Home' ), gutenberg_block_core_breadcrumbs_is_paged() );
40 }
41 }
42
43 // Handle home.
44 if ( $is_home ) {
45 // These checks are explicitly nested in order not to execute the `else` branch.
46 if ( $page_for_posts ) {
47 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item( gutenberg_block_core_breadcrumbs_get_post_title( $page_for_posts ), gutenberg_block_core_breadcrumbs_is_paged() );
48 }
49 if ( gutenberg_block_core_breadcrumbs_is_paged() ) {
50 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
51 }
52 } elseif ( $is_front_page ) {
53 // Handle front page.
54 // This check is explicitly nested in order not to execute the `else` branch.
55 // If front page is set to custom page and is paged, add the page number.
56 if ( (int) get_query_var( 'page' ) > 1 ) {
57 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item( 'page' );
58 }
59 } elseif ( is_search() ) {
60 // Handle search results.
61 $is_paged = gutenberg_block_core_breadcrumbs_is_paged();
62 /* translators: %s: search query */
63 $text = sprintf( __( 'Search results for: "%s"' ), wp_trim_words( get_search_query(), 10 ) );
64 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item( $text, $is_paged );
65 // Add the "Page X" as the current page if paginated.
66 if ( $is_paged ) {
67 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
68 }
69 } elseif ( is_404() ) {
70 // Handle 404 pages.
71 $breadcrumb_items[] = array(
72 'label' => __( 'Page not found' ),
73 );
74 } elseif ( is_archive() ) {
75 // Handle archive pages (taxonomy, post type, date, author archives).
76 $archive_breadcrumbs = gutenberg_block_core_breadcrumbs_get_archive_breadcrumbs();
77 if ( ! empty( $archive_breadcrumbs ) ) {
78 $breadcrumb_items = array_merge( $breadcrumb_items, $archive_breadcrumbs );
79 }
80 } else {
81 // Handle single post/page breadcrumbs.
82 if ( ! isset( $block->context['postId'] ) || ! isset( $block->context['postType'] ) ) {
83 return '';
84 }
85
86 $post_id = $block->context['postId'];
87 $post_type = $block->context['postType'];
88
89 $post = get_post( $post_id );
90 if ( ! $post ) {
91 return '';
92 }
93
94 // For non-hierarchical post types with parents (e.g., attachments), build trail for the parent.
95 $post_parent = $post->post_parent;
96 $parent_post = null;
97 if ( ! is_post_type_hierarchical( $post_type ) && $post_parent ) {
98 $parent_post = get_post( $post_parent );
99 if ( $parent_post ) {
100 $post_id = $parent_post->ID;
101 $post_type = $parent_post->post_type;
102 $post_parent = $parent_post->post_parent;
103 }
104 }
105
106 // Determine breadcrumb type.
107 // Some non-hierarchical post types (e.g., attachments) can have parents.
108 // Use hierarchical breadcrumbs if a parent exists, otherwise use taxonomy breadcrumbs.
109 $show_terms = false;
110 if ( ! is_post_type_hierarchical( $post_type ) && ! $post_parent ) {
111 $show_terms = true;
112 } elseif ( empty( get_object_taxonomies( $post_type, 'objects' ) ) ) {
113 $show_terms = false;
114 } else {
115 $show_terms = $attributes['prefersTaxonomy'];
116 }
117
118 // Add post type archive link if applicable.
119 $post_type_object = get_post_type_object( $post_type );
120 $archive_link = get_post_type_archive_link( $post_type );
121 if ( $archive_link && untrailingslashit( home_url() ) !== untrailingslashit( $archive_link ) ) {
122 $label = $post_type_object->labels->archives;
123 if ( 'post' === $post_type && $page_for_posts ) {
124 $label = gutenberg_block_core_breadcrumbs_get_post_title( $page_for_posts );
125 }
126 $breadcrumb_items[] = array(
127 'label' => $label,
128 'url' => $archive_link,
129 );
130 }
131 // Build breadcrumb trail based on hierarchical structure or taxonomy terms.
132 if ( ! $show_terms ) {
133 $breadcrumb_items = array_merge( $breadcrumb_items, gutenberg_block_core_breadcrumbs_get_hierarchical_post_type_breadcrumbs( $post_id ) );
134 } else {
135 $breadcrumb_items = array_merge( $breadcrumb_items, gutenberg_block_core_breadcrumbs_get_terms_breadcrumbs( $post_id, $post_type ) );
136 }
137
138 // Add post title: linked when viewing a paginated page, plain text otherwise.
139 $is_paged = (int) get_query_var( 'page' ) > 1 || (int) get_query_var( 'cpage' ) > 1;
140 $title = gutenberg_block_core_breadcrumbs_get_post_title( $post );
141
142 if ( $is_paged ) {
143 $breadcrumb_items[] = array(
144 'label' => $title,
145 'url' => get_permalink( $post ),
146 'allow_html' => true,
147 );
148 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item( (int) get_query_var( 'cpage' ) > 1 ? 'cpage' : 'page' );
149 } else {
150 $breadcrumb_items[] = array(
151 'label' => $title,
152 'allow_html' => true,
153 );
154 }
155 }
156
157 // Remove current item if disabled.
158 if ( ! $attributes['showCurrentItem'] && ! empty( $breadcrumb_items ) ) {
159 array_pop( $breadcrumb_items );
160 }
161
162 /**
163 * Filters the breadcrumb items array before rendering.
164 *
165 * Allows developers to modify, add, or remove breadcrumb items.
166 *
167 * @since 7.0.0
168 *
169 * @param array[] $breadcrumb_items {
170 * Array of breadcrumb item data.
171 *
172 * @type string $label The breadcrumb text.
173 * @type string $url Optional. The breadcrumb link URL.
174 * @type bool $allow_html Optional. Whether to allow HTML in the label.
175 * When true, the label will be sanitized with wp_kses_post(),
176 * allowing only safe HTML tags. When false or omitted, all HTML
177 * will be escaped with esc_html(). Default false.
178 * }
179 */
180 $breadcrumb_items = apply_filters( 'block_core_breadcrumbs_items', $breadcrumb_items );
181
182 if ( empty( $breadcrumb_items ) ) {
183 return '';
184 }
185
186 $wrapper_attributes = get_block_wrapper_attributes(
187 array(
188 'style' => '--separator: "' . addcslashes( $attributes['separator'], '\\"' ) . '";',
189 'aria-label' => __( 'Breadcrumbs' ),
190 )
191 );
192
193 $breadcrumb_html = sprintf(
194 '<nav %s><ol>%s</ol></nav>',
195 $wrapper_attributes,
196 implode(
197 '',
198 array_map(
199 static function ( $item ) {
200 $label = ! empty( $item['allow_html'] ) ? wp_kses_post( $item['label'] ) : esc_html( $item['label'] );
201 if ( ! empty( $item['url'] ) ) {
202 return '<li><a href="' . esc_url( $item['url'] ) . '">' . $label . '</a></li>';
203 }
204 return '<li><span aria-current="page">' . $label . '</span></li>';
205 },
206 $breadcrumb_items
207 )
208 )
209 );
210
211 return $breadcrumb_html;
212 }
213
214 /**
215 * Checks if we're on a paginated view (page 2 or higher).
216 *
217 * @since 7.0.0
218 *
219 * @return bool True if paged > 1, false otherwise.
220 */
221 function gutenberg_block_core_breadcrumbs_is_paged() {
222 $paged = (int) get_query_var( 'paged' );
223 return $paged > 1;
224 }
225
226 /**
227 * Creates a "Page X" breadcrumb item for paginated views.
228 *
229 * @since 7.0.0
230 * @param string $query_var Optional. Query variable to get current page number. Default 'paged'.
231 * @return array The "Page X" breadcrumb item data.
232 */
233 function gutenberg_block_core_breadcrumbs_create_page_number_item( $query_var = 'paged' ) {
234 $paged = (int) get_query_var( $query_var );
235
236 if ( 'cpage' === $query_var ) {
237 return array(
238 'label' => sprintf(
239 /* translators: %s: comment page number */
240 __( 'Comments Page %s' ),
241 number_format_i18n( $paged )
242 ),
243 );
244 }
245
246 return array(
247 'label' => sprintf(
248 /* translators: %s: page number */
249 __( 'Page %s' ),
250 number_format_i18n( $paged )
251 ),
252 );
253 }
254
255
256 /**
257 * Creates a breadcrumb item that's either a link or current page item.
258 *
259 * When paginated (is_paged is true), creates a link to page 1.
260 * Otherwise, creates a span marked as the current page.
261 *
262 * @since 7.0.0
263 *
264 * @param string $text The text content.
265 * @param bool $is_paged Whether we're on a paginated view.
266 *
267 * @return array The breadcrumb item data.
268 */
269 function gutenberg_block_core_breadcrumbs_create_item( $text, $is_paged = false ) {
270 $item = array( 'label' => $text );
271 if ( $is_paged ) {
272 $item['url'] = get_pagenum_link( 1 );
273 }
274 return $item;
275 }
276
277 /**
278 * Gets a post title with fallback for empty titles.
279 *
280 * @since 7.0.0
281 *
282 * @param int|WP_Post $post_id_or_object The post ID or post object.
283 *
284 * @return string The post title or fallback text.
285 */
286 function gutenberg_block_core_breadcrumbs_get_post_title( $post_id_or_object ) {
287 $title = get_the_title( $post_id_or_object );
288 if ( strlen( $title ) === 0 ) {
289 $title = __( '(no title)' );
290 }
291 return $title;
292 }
293
294 /**
295 * Generates breadcrumb items from hierarchical post type ancestors.
296 *
297 * @since 7.0.0
298 *
299 * @param int $post_id The post ID.
300 *
301 * @return array Array of breadcrumb item data.
302 */
303 function gutenberg_block_core_breadcrumbs_get_hierarchical_post_type_breadcrumbs( $post_id ) {
304 $breadcrumb_items = array();
305 $ancestors = get_post_ancestors( $post_id );
306 $ancestors = array_reverse( $ancestors );
307
308 foreach ( $ancestors as $ancestor_id ) {
309 $breadcrumb_items[] = array(
310 'label' => gutenberg_block_core_breadcrumbs_get_post_title( $ancestor_id ),
311 'url' => get_permalink( $ancestor_id ),
312 'allow_html' => true,
313 );
314 }
315 return $breadcrumb_items;
316 }
317
318 /**
319 * Generates breadcrumb items for hierarchical term ancestors.
320 *
321 * For hierarchical taxonomies, retrieves and formats ancestor terms as breadcrumb links.
322 *
323 * @since 7.0.0
324 *
325 * @param int $term_id The term ID.
326 * @param string $taxonomy The taxonomy name.
327 *
328 * @return array Array of breadcrumb item data for ancestors.
329 */
330 function gutenberg_block_core_breadcrumbs_get_term_ancestors_items( $term_id, $taxonomy ) {
331 $breadcrumb_items = array();
332
333 // Check if taxonomy is hierarchical and add ancestor term links.
334 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
335 $term_ancestors = get_ancestors( $term_id, $taxonomy, 'taxonomy' );
336 $term_ancestors = array_reverse( $term_ancestors );
337 foreach ( $term_ancestors as $ancestor_id ) {
338 $ancestor_term = get_term( $ancestor_id, $taxonomy );
339 if ( $ancestor_term && ! is_wp_error( $ancestor_term ) ) {
340 $breadcrumb_items[] = array(
341 'label' => $ancestor_term->name,
342 'url' => get_term_link( $ancestor_term ),
343 );
344 }
345 }
346 }
347
348 return $breadcrumb_items;
349 }
350
351 /**
352 * Generates breadcrumb items for archive pages.
353 *
354 * Handles taxonomy archives, post type archives, date archives, and author archives.
355 * For hierarchical taxonomies, includes ancestor terms in the breadcrumb trail.
356 *
357 * @since 7.0.0
358 *
359 * @return array Array of breadcrumb item data.
360 */
361 function gutenberg_block_core_breadcrumbs_get_archive_breadcrumbs() {
362 $breadcrumb_items = array();
363
364 // Date archive (check first since it doesn't have a queried object).
365 if ( is_date() ) {
366 $year = get_query_var( 'year' );
367 $month = get_query_var( 'monthnum' );
368 $day = get_query_var( 'day' );
369
370 // Fallback to 'm' query var for plain permalinks.
371 // Plain permalinks use ?m=YYYYMMDD format instead of separate query vars.
372 if ( ! $year ) {
373 $m = get_query_var( 'm' );
374 if ( $m ) {
375 $year = substr( $m, 0, 4 );
376 $month = substr( $m, 4, 2 );
377 $day = (int) substr( $m, 6, 2 );
378 }
379 }
380
381 $is_paged = gutenberg_block_core_breadcrumbs_is_paged();
382
383 if ( $year ) {
384 if ( $month ) {
385 // Year is linked if we have month.
386 $breadcrumb_items[] = array(
387 'label' => $year,
388 'url' => get_year_link( $year ),
389 );
390
391 if ( $day ) {
392 // Month is linked if we have day.
393 $breadcrumb_items[] = array(
394 'label' => date_i18n( 'F', mktime( 0, 0, 0, $month, 1, $year ) ),
395 'url' => get_month_link( $year, $month ),
396 );
397 // Add day (current if not paginated, link if paginated).
398 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
399 $day,
400 $is_paged
401 );
402 } else {
403 // Add month (current if not paginated, link if paginated).
404 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
405 date_i18n( 'F', mktime( 0, 0, 0, $month, 1, $year ) ),
406 $is_paged
407 );
408 }
409 } else {
410 // Add year (current if not paginated, link if paginated).
411 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
412 $year,
413 $is_paged
414 );
415 }
416 }
417
418 // Add pagination breadcrumb if on a paged date archive.
419 if ( $is_paged ) {
420 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
421 }
422
423 return $breadcrumb_items;
424 }
425
426 // For other archive types, we need a queried object.
427 $queried_object = get_queried_object();
428
429 if ( ! $queried_object ) {
430 return array();
431 }
432
433 $is_paged = gutenberg_block_core_breadcrumbs_is_paged();
434
435 // Taxonomy archive (category, tag, custom taxonomy).
436 if ( $queried_object instanceof WP_Term ) {
437 $term = $queried_object;
438 $taxonomy = $term->taxonomy;
439
440 // Add hierarchical term ancestors if applicable.
441 $breadcrumb_items = array_merge(
442 $breadcrumb_items,
443 gutenberg_block_core_breadcrumbs_get_term_ancestors_items( $term->term_id, $taxonomy )
444 );
445
446 // Add current term (current if not paginated, link if paginated).
447 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
448 $term->name,
449 $is_paged
450 );
451 } elseif ( is_post_type_archive() ) {
452 // Post type archive.
453 $post_type = get_query_var( 'post_type' );
454 if ( is_array( $post_type ) ) {
455 $post_type = reset( $post_type );
456 }
457 $post_type_object = get_post_type_object( $post_type );
458
459 /** This filter is documented in wp-includes/general-template.php */
460 $title = apply_filters( 'post_type_archive_title', $post_type_object->labels->archives, $post_type ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
461
462 if ( $post_type_object ) {
463 // Add post type (current if not paginated, link if paginated).
464 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
465 $title ? $title : $post_type_object->labels->archives,
466 $is_paged
467 );
468 }
469 } elseif ( is_author() ) {
470 // Author archive.
471 $author = $queried_object;
472 // Add author (current if not paginated, link if paginated).
473 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
474 $author->display_name,
475 $is_paged
476 );
477 }
478
479 // Add pagination breadcrumb if on a paged archive.
480 if ( $is_paged ) {
481 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
482 }
483
484 return $breadcrumb_items;
485 }
486
487 /**
488 * Generates breadcrumb items from taxonomy terms.
489 *
490 * Finds the first publicly queryable taxonomy with terms assigned to the post
491 * and generates breadcrumb links, including hierarchical term ancestors if applicable.
492 *
493 * @since 7.0.0
494 *
495 * @param int $post_id The post ID.
496 * @param string $post_type The post type name.
497 *
498 * @return array Array of breadcrumb item data.
499 */
500 function gutenberg_block_core_breadcrumbs_get_terms_breadcrumbs( $post_id, $post_type ) {
501 $breadcrumb_items = array();
502
503 // Get public taxonomies for this post type.
504 $taxonomies = wp_filter_object_list(
505 get_object_taxonomies( $post_type, 'objects' ),
506 array(
507 'publicly_queryable' => true,
508 'show_in_rest' => true,
509 )
510 );
511
512 if ( empty( $taxonomies ) ) {
513 return $breadcrumb_items;
514 }
515
516 /**
517 * Filters breadcrumb settings (taxonomy and term selection) for a post or post type.
518 *
519 * Allows developers to specify which taxonomy and term should be used in the
520 * breadcrumb trail when a post type has multiple taxonomies or when a post is
521 * assigned to multiple terms within a taxonomy.
522 *
523 * @since 7.0.0
524 *
525 * @param array $settings {
526 * Array of breadcrumb settings. Default empty array.
527 *
528 * @type string $taxonomy Optional. Taxonomy slug to use for breadcrumbs.
529 * The taxonomy must be registered for the post type and have
530 * terms assigned to the post. If not found or has no terms,
531 * fall back to the first available taxonomy with terms.
532 * @type string $term Optional. Term slug to use when the post has multiple terms
533 * in the selected taxonomy. If the term is not found or not
534 * assigned to the post, fall back to the first term. If the
535 * post has only one term, that term is used regardless.
536 * }
537 * @param string $post_type The post type slug.
538 * @param int $post_id The post ID.
539 */
540 $settings = apply_filters( 'block_core_breadcrumbs_post_type_settings', array(), $post_type, $post_id );
541
542 $taxonomy_name = null;
543 $terms = array();
544
545 // Try preferred taxonomy first if specified.
546 if ( ! empty( $settings['taxonomy'] ) ) {
547 foreach ( $taxonomies as $taxonomy ) {
548 if ( $taxonomy->name === $settings['taxonomy'] ) {
549 $post_terms = get_the_terms( $post_id, $taxonomy->name );
550 if ( ! empty( $post_terms ) && ! is_wp_error( $post_terms ) ) {
551 $taxonomy_name = $taxonomy->name;
552 $terms = $post_terms;
553 }
554 break;
555 }
556 }
557 }
558
559 // If no preferred taxonomy or it didn't have terms, find the first taxonomy with terms.
560 if ( empty( $terms ) ) {
561 foreach ( $taxonomies as $taxonomy ) {
562 $post_terms = get_the_terms( $post_id, $taxonomy->name );
563 if ( ! empty( $post_terms ) && ! is_wp_error( $post_terms ) ) {
564 $taxonomy_name = $taxonomy->name;
565 $terms = $post_terms;
566 break;
567 }
568 }
569 }
570
571 if ( ! empty( $terms ) ) {
572 // Select which term to use.
573 $term = reset( $terms );
574
575 // Try preferred term if specified and post has multiple terms.
576 if ( ! empty( $settings['term'] ) && count( $terms ) > 1 ) {
577 foreach ( $terms as $candidate_term ) {
578 if ( $candidate_term->slug === $settings['term'] ) {
579 $term = $candidate_term;
580 break;
581 }
582 }
583 }
584
585 // Add hierarchical term ancestors if applicable.
586 $breadcrumb_items = array_merge(
587 $breadcrumb_items,
588 gutenberg_block_core_breadcrumbs_get_term_ancestors_items( $term->term_id, $taxonomy_name )
589 );
590 $breadcrumb_items[] = array(
591 'label' => $term->name,
592 'url' => get_term_link( $term ),
593 );
594 }
595 return $breadcrumb_items;
596 }
597
598 /**
599 * Registers the `core/breadcrumbs` block on the server.
600 *
601 * @since 7.0.0
602 */
603 function gutenberg_register_block_core_breadcrumbs() {
604 register_block_type_from_metadata(
605 __DIR__ . '/breadcrumbs',
606 array(
607 'render_callback' => 'gutenberg_render_block_core_breadcrumbs',
608 )
609 );
610 }
611 add_action( 'init', 'gutenberg_register_block_core_breadcrumbs', 20 );
612