PluginProbe
Gutenberg / 23.7.2
Gutenberg v23.7.2
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 23.7.2, at build/scripts/block-library/breadcrumbs.php

614 lines 19.1 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 $separator_attr = $attributes['separator'] ?? null;
187 $separator = is_string( $separator_attr ) ? addcslashes( $separator_attr, '\\"' ) : '';
188 $wrapper_attributes = get_block_wrapper_attributes(
189 array(
190 'style' => '--separator: "' . $separator . '";',
191 'aria-label' => __( 'Breadcrumbs' ),
192 )
193 );
194
195 $breadcrumb_html = sprintf(
196 '<nav %s><ol>%s</ol></nav>',
197 $wrapper_attributes,
198 implode(
199 '',
200 array_map(
201 static function ( $item ) {
202 $label = ! empty( $item['allow_html'] ) ? wp_kses_post( $item['label'] ) : esc_html( $item['label'] );
203 if ( ! empty( $item['url'] ) ) {
204 return '<li><a href="' . esc_url( $item['url'] ) . '">' . $label . '</a></li>';
205 }
206 return '<li><span aria-current="page">' . $label . '</span></li>';
207 },
208 $breadcrumb_items
209 )
210 )
211 );
212
213 return $breadcrumb_html;
214 }
215
216 /**
217 * Checks if we're on a paginated view (page 2 or higher).
218 *
219 * @since 7.0.0
220 *
221 * @return bool True if paged > 1, false otherwise.
222 */
223 function gutenberg_block_core_breadcrumbs_is_paged() {
224 $paged = (int) get_query_var( 'paged' );
225 return $paged > 1;
226 }
227
228 /**
229 * Creates a "Page X" breadcrumb item for paginated views.
230 *
231 * @since 7.0.0
232 * @param string $query_var Optional. Query variable to get current page number. Default 'paged'.
233 * @return array The "Page X" breadcrumb item data.
234 */
235 function gutenberg_block_core_breadcrumbs_create_page_number_item( $query_var = 'paged' ) {
236 $paged = (int) get_query_var( $query_var );
237
238 if ( 'cpage' === $query_var ) {
239 return array(
240 'label' => sprintf(
241 /* translators: %s: comment page number */
242 __( 'Comments Page %s' ),
243 number_format_i18n( $paged )
244 ),
245 );
246 }
247
248 return array(
249 'label' => sprintf(
250 /* translators: %s: page number */
251 __( 'Page %s' ),
252 number_format_i18n( $paged )
253 ),
254 );
255 }
256
257
258 /**
259 * Creates a breadcrumb item that's either a link or current page item.
260 *
261 * When paginated (is_paged is true), creates a link to page 1.
262 * Otherwise, creates a span marked as the current page.
263 *
264 * @since 7.0.0
265 *
266 * @param string $text The text content.
267 * @param bool $is_paged Whether we're on a paginated view.
268 *
269 * @return array The breadcrumb item data.
270 */
271 function gutenberg_block_core_breadcrumbs_create_item( $text, $is_paged = false ) {
272 $item = array( 'label' => $text );
273 if ( $is_paged ) {
274 $item['url'] = get_pagenum_link( 1 );
275 }
276 return $item;
277 }
278
279 /**
280 * Gets a post title with fallback for empty titles.
281 *
282 * @since 7.0.0
283 *
284 * @param int|WP_Post $post_id_or_object The post ID or post object.
285 *
286 * @return string The post title or fallback text.
287 */
288 function gutenberg_block_core_breadcrumbs_get_post_title( $post_id_or_object ) {
289 $title = get_the_title( $post_id_or_object );
290 if ( strlen( $title ) === 0 ) {
291 $title = __( '(no title)' );
292 }
293 return $title;
294 }
295
296 /**
297 * Generates breadcrumb items from hierarchical post type ancestors.
298 *
299 * @since 7.0.0
300 *
301 * @param int $post_id The post ID.
302 *
303 * @return array Array of breadcrumb item data.
304 */
305 function gutenberg_block_core_breadcrumbs_get_hierarchical_post_type_breadcrumbs( $post_id ) {
306 $breadcrumb_items = array();
307 $ancestors = get_post_ancestors( $post_id );
308 $ancestors = array_reverse( $ancestors );
309
310 foreach ( $ancestors as $ancestor_id ) {
311 $breadcrumb_items[] = array(
312 'label' => gutenberg_block_core_breadcrumbs_get_post_title( $ancestor_id ),
313 'url' => get_permalink( $ancestor_id ),
314 'allow_html' => true,
315 );
316 }
317 return $breadcrumb_items;
318 }
319
320 /**
321 * Generates breadcrumb items for hierarchical term ancestors.
322 *
323 * For hierarchical taxonomies, retrieves and formats ancestor terms as breadcrumb links.
324 *
325 * @since 7.0.0
326 *
327 * @param int $term_id The term ID.
328 * @param string $taxonomy The taxonomy name.
329 *
330 * @return array Array of breadcrumb item data for ancestors.
331 */
332 function gutenberg_block_core_breadcrumbs_get_term_ancestors_items( $term_id, $taxonomy ) {
333 $breadcrumb_items = array();
334
335 // Check if taxonomy is hierarchical and add ancestor term links.
336 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
337 $term_ancestors = get_ancestors( $term_id, $taxonomy, 'taxonomy' );
338 $term_ancestors = array_reverse( $term_ancestors );
339 foreach ( $term_ancestors as $ancestor_id ) {
340 $ancestor_term = get_term( $ancestor_id, $taxonomy );
341 if ( $ancestor_term && ! is_wp_error( $ancestor_term ) ) {
342 $breadcrumb_items[] = array(
343 'label' => $ancestor_term->name,
344 'url' => get_term_link( $ancestor_term ),
345 );
346 }
347 }
348 }
349
350 return $breadcrumb_items;
351 }
352
353 /**
354 * Generates breadcrumb items for archive pages.
355 *
356 * Handles taxonomy archives, post type archives, date archives, and author archives.
357 * For hierarchical taxonomies, includes ancestor terms in the breadcrumb trail.
358 *
359 * @since 7.0.0
360 *
361 * @return array Array of breadcrumb item data.
362 */
363 function gutenberg_block_core_breadcrumbs_get_archive_breadcrumbs() {
364 $breadcrumb_items = array();
365
366 // Date archive (check first since it doesn't have a queried object).
367 if ( is_date() ) {
368 $year = get_query_var( 'year' );
369 $month = get_query_var( 'monthnum' );
370 $day = get_query_var( 'day' );
371
372 // Fallback to 'm' query var for plain permalinks.
373 // Plain permalinks use ?m=YYYYMMDD format instead of separate query vars.
374 if ( ! $year ) {
375 $m = get_query_var( 'm' );
376 if ( $m ) {
377 $year = substr( $m, 0, 4 );
378 $month = substr( $m, 4, 2 );
379 $day = (int) substr( $m, 6, 2 );
380 }
381 }
382
383 $is_paged = gutenberg_block_core_breadcrumbs_is_paged();
384
385 if ( $year ) {
386 if ( $month ) {
387 // Year is linked if we have month.
388 $breadcrumb_items[] = array(
389 'label' => $year,
390 'url' => get_year_link( $year ),
391 );
392
393 if ( $day ) {
394 // Month is linked if we have day.
395 $breadcrumb_items[] = array(
396 'label' => date_i18n( 'F', mktime( 0, 0, 0, $month, 1, $year ) ),
397 'url' => get_month_link( $year, $month ),
398 );
399 // Add day (current if not paginated, link if paginated).
400 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
401 $day,
402 $is_paged
403 );
404 } else {
405 // Add month (current if not paginated, link if paginated).
406 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
407 date_i18n( 'F', mktime( 0, 0, 0, $month, 1, $year ) ),
408 $is_paged
409 );
410 }
411 } else {
412 // Add year (current if not paginated, link if paginated).
413 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
414 $year,
415 $is_paged
416 );
417 }
418 }
419
420 // Add pagination breadcrumb if on a paged date archive.
421 if ( $is_paged ) {
422 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
423 }
424
425 return $breadcrumb_items;
426 }
427
428 // For other archive types, we need a queried object.
429 $queried_object = get_queried_object();
430
431 if ( ! $queried_object ) {
432 return array();
433 }
434
435 $is_paged = gutenberg_block_core_breadcrumbs_is_paged();
436
437 // Taxonomy archive (category, tag, custom taxonomy).
438 if ( $queried_object instanceof WP_Term ) {
439 $term = $queried_object;
440 $taxonomy = $term->taxonomy;
441
442 // Add hierarchical term ancestors if applicable.
443 $breadcrumb_items = array_merge(
444 $breadcrumb_items,
445 gutenberg_block_core_breadcrumbs_get_term_ancestors_items( $term->term_id, $taxonomy )
446 );
447
448 // Add current term (current if not paginated, link if paginated).
449 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
450 $term->name,
451 $is_paged
452 );
453 } elseif ( is_post_type_archive() ) {
454 // Post type archive.
455 $post_type = get_query_var( 'post_type' );
456 if ( is_array( $post_type ) ) {
457 $post_type = reset( $post_type );
458 }
459 $post_type_object = get_post_type_object( $post_type );
460
461 /** This filter is documented in wp-includes/general-template.php */
462 $title = apply_filters( 'post_type_archive_title', $post_type_object->labels->archives, $post_type ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
463
464 if ( $post_type_object ) {
465 // Add post type (current if not paginated, link if paginated).
466 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
467 $title ? $title : $post_type_object->labels->archives,
468 $is_paged
469 );
470 }
471 } elseif ( is_author() ) {
472 // Author archive.
473 $author = $queried_object;
474 // Add author (current if not paginated, link if paginated).
475 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_item(
476 $author->display_name,
477 $is_paged
478 );
479 }
480
481 // Add pagination breadcrumb if on a paged archive.
482 if ( $is_paged ) {
483 $breadcrumb_items[] = gutenberg_block_core_breadcrumbs_create_page_number_item();
484 }
485
486 return $breadcrumb_items;
487 }
488
489 /**
490 * Generates breadcrumb items from taxonomy terms.
491 *
492 * Finds the first publicly queryable taxonomy with terms assigned to the post
493 * and generates breadcrumb links, including hierarchical term ancestors if applicable.
494 *
495 * @since 7.0.0
496 *
497 * @param int $post_id The post ID.
498 * @param string $post_type The post type name.
499 *
500 * @return array Array of breadcrumb item data.
501 */
502 function gutenberg_block_core_breadcrumbs_get_terms_breadcrumbs( $post_id, $post_type ) {
503 $breadcrumb_items = array();
504
505 // Get public taxonomies for this post type.
506 $taxonomies = wp_filter_object_list(
507 get_object_taxonomies( $post_type, 'objects' ),
508 array(
509 'publicly_queryable' => true,
510 'show_in_rest' => true,
511 )
512 );
513
514 if ( empty( $taxonomies ) ) {
515 return $breadcrumb_items;
516 }
517
518 /**
519 * Filters breadcrumb settings (taxonomy and term selection) for a post or post type.
520 *
521 * Allows developers to specify which taxonomy and term should be used in the
522 * breadcrumb trail when a post type has multiple taxonomies or when a post is
523 * assigned to multiple terms within a taxonomy.
524 *
525 * @since 7.0.0
526 *
527 * @param array $settings {
528 * Array of breadcrumb settings. Default empty array.
529 *
530 * @type string $taxonomy Optional. Taxonomy slug to use for breadcrumbs.
531 * The taxonomy must be registered for the post type and have
532 * terms assigned to the post. If not found or has no terms,
533 * fall back to the first available taxonomy with terms.
534 * @type string $term Optional. Term slug to use when the post has multiple terms
535 * in the selected taxonomy. If the term is not found or not
536 * assigned to the post, fall back to the first term. If the
537 * post has only one term, that term is used regardless.
538 * }
539 * @param string $post_type The post type slug.
540 * @param int $post_id The post ID.
541 */
542 $settings = apply_filters( 'block_core_breadcrumbs_post_type_settings', array(), $post_type, $post_id );
543
544 $taxonomy_name = null;
545 $terms = array();
546
547 // Try preferred taxonomy first if specified.
548 if ( ! empty( $settings['taxonomy'] ) ) {
549 foreach ( $taxonomies as $taxonomy ) {
550 if ( $taxonomy->name === $settings['taxonomy'] ) {
551 $post_terms = get_the_terms( $post_id, $taxonomy->name );
552 if ( ! empty( $post_terms ) && ! is_wp_error( $post_terms ) ) {
553 $taxonomy_name = $taxonomy->name;
554 $terms = $post_terms;
555 }
556 break;
557 }
558 }
559 }
560
561 // If no preferred taxonomy or it didn't have terms, find the first taxonomy with terms.
562 if ( empty( $terms ) ) {
563 foreach ( $taxonomies as $taxonomy ) {
564 $post_terms = get_the_terms( $post_id, $taxonomy->name );
565 if ( ! empty( $post_terms ) && ! is_wp_error( $post_terms ) ) {
566 $taxonomy_name = $taxonomy->name;
567 $terms = $post_terms;
568 break;
569 }
570 }
571 }
572
573 if ( ! empty( $terms ) ) {
574 // Select which term to use.
575 $term = reset( $terms );
576
577 // Try preferred term if specified and post has multiple terms.
578 if ( ! empty( $settings['term'] ) && count( $terms ) > 1 ) {
579 foreach ( $terms as $candidate_term ) {
580 if ( $candidate_term->slug === $settings['term'] ) {
581 $term = $candidate_term;
582 break;
583 }
584 }
585 }
586
587 // Add hierarchical term ancestors if applicable.
588 $breadcrumb_items = array_merge(
589 $breadcrumb_items,
590 gutenberg_block_core_breadcrumbs_get_term_ancestors_items( $term->term_id, $taxonomy_name )
591 );
592 $breadcrumb_items[] = array(
593 'label' => $term->name,
594 'url' => get_term_link( $term ),
595 );
596 }
597 return $breadcrumb_items;
598 }
599
600 /**
601 * Registers the `core/breadcrumbs` block on the server.
602 *
603 * @since 7.0.0
604 */
605 function gutenberg_register_block_core_breadcrumbs() {
606 register_block_type_from_metadata(
607 __DIR__ . '/breadcrumbs',
608 array(
609 'render_callback' => 'gutenberg_render_block_core_breadcrumbs',
610 )
611 );
612 }
613 add_action( 'init', 'gutenberg_register_block_core_breadcrumbs', 20 );
614